updated docs

This commit is contained in:
dzsekijo 2006-06-06 07:00:07 +00:00
parent 9a252a04d9
commit fa051fba9b
4 changed files with 233 additions and 82 deletions

View file

@ -5,6 +5,7 @@
classes
* Add support for querying any fs method in feature requester.
* Replace `foo.has_key(bar)' with `bar in foo'.
* Updated docs.
2006-06-04 Csaba Henk <csaba.henk@creo.hu>
* Added support for the rest of FUSE methods:

24
INSTALL
View file

@ -1,9 +1,25 @@
REQUIREMENTS:
- FUSE 2.* (tested with FUSE 2.3 and newer)
- Python 2.3 or newer (tested with 2.4, but should work with 2.3;
even 2.2 should be possible to use with some additonal imports
[Optik/optparse, generators from future])
- pkg-config [http://pkgconfig.freedesktop.org/]
INSTALLATION:
The best way to install this python FUSE module is:
1. make sure the rest of FUSE (incl libfuse.a or libfuse.so) has built
successfully
2. type 'python setup.py build'
3. if all has gone ok, become root and type 'python setup.py install'
1. Type 'python setup.py build'. (If you have FUSE installed at a
non-standard location, adjust the PKG_CONFIG_PATH environment variable
accordingly.)
2. You might try xmp.py, the example filesystem. Just link
build/lib.*/_fusemodule.so to the top of the source tree and
see what "python xmp.py -h" gives.
3. If all has gone ok, become root and type 'python setup.py install'.
That way, the FUSE python modules will be built against the correct version
of python and installed in your system-wide python directory. This will allow
your filesystem script to find them, no matter where it's residing.
WARNING:
We have altered from the original FUSE Python API in non-compatible
ways. See README.new_fusepy_api how can you get running a fs which is
written against the original API.

3
README
View file

@ -1,3 +1,6 @@
WARNING:
THIS FILE DOESN'T MATCH CURRENT STATE OF THE AFFAIRS.
Refer to the INSTALL file for build/install instructions
General Information

View file

@ -1,20 +1,19 @@
============================
FUSE-Python bindings new API
============================
:Author: Csaba Henk
I've made several changes on the FUSE Python interface as we knew it.
Let's see how it effects the usage of the module -- both from the end
user and the developer POV.
.. contents::
.. Attention:: The code is being adjusted so that it can sanely use
newer FUSE features.
This document will not be updated until the end of this period.
Moreover, you can count on temporary breaks in compatibility
with oldish code (either C or Python).
Old API
===================
Enforcing compatibility
-----------------------
@ -30,107 +29,121 @@ your filesystem. This can be achieved externally too, by setting the
What's incompatible, anyway?
----------------------------
We don't set FUSE library parameters via direct instance attributes of
``Fuse``. We got rid of ``Fuse#mountpoint``, ``Fuse#debug``,
``Fuse#allow_other`` and ``Fuse#keep_cache``. [#]_
- ``Fuse`` instance initialization
All these characteristics are kept in a ``FuseArgs`` instance. That is,
if you wanna set the mountpoint, do like
``aFuse.fuse_args.mountpoint = mp``; if you want the ``keep_cache``
property, do like ``aFuse.fuse_args.add('keep_cache')``.
- Handling of command line options
Specifying the mountpoint as an argument of the ``Fuse`` constructor
will not work. The simplistic parsing routine which we had in
``Fuse#__init__()`` is no longer there. So when you instantiate
``Fuse``, you get an instance with no mountpoint set. The
``Fuse#optlist`` and ``Fuse#optdict`` attributes will neither be set.
- Transferring structured system data (file/filesystem attributes, directory
entries) to the FUSE library
If you rely on these constructs, then one way of updating the code to
the new API is to reimplement the aforementioned simplistic parsing
routine (that's a short piece of code). However, you are encouraged to
switch to the new parsing interface, which serves you with easy but
powerful commandline parsing.
- `getdir` fs method ditched in favor of `readdir`
We also changed the ``getattr`` and ``statfs`` fs methods.
We switched to an object oriented interface. Instead of returning a
sequence, you have to return an object with appropriate attributes
(if any of them is lacking, the fs user will get an ``EINVAL``).
For ``getattr``, the attributes are just like those of the return
value of ``os.stat()``: ``st_mode``, ``st_ino``, ... For ``statfs``,
the attributes are just like those of the return value of ``os.statvfs()``:
``f_bsize``, ``f_frsize``, ... [#]_
That is, to upgrade your filesystem to the new API, you will have to
rewrite:
If you start from scratch (ie., you are not passing on an ``os.stat()`` or
``os.statvfs()`` result), you can use the auxiliary classes ``fuse.Stat`` and
``fuse.StatVFS`` for instantiating appropriate objects. For ``fuse.Stat``, you
have to define each of these attributes, for ``fuse.StatVfs`` they are initated
with a 0 default value [#]_.
- all your code between instantiating ``Fuse`` and calling the
``main()`` method of the instance (and the ``__init__()``, ``main()``
methods of your ``Fuse`` derived class, if you have overwritten the
original)
.. _statvfs: http://docs.python.org/lib/module-statvfs.html
- the following fs methods: `getattr`, `statfs`, `getdir`.
.. [#] We follow the convention that we refer to instance attributes like
``Klass#attr``. If it's a method, we'll use ``Klass#meth()``.
.. [#] Traditionally, ``os.stat()`` and ``os.statvfs()`` returned tuples.
Since Python 2.2, they return dedicated objects which both implement the
sequence protocol and have the aforementioned attributes (when you print
them, they look like a tuple).
.. [#] We might go stricter and leave some of the ``statfs`` attributes
undef'd.
New API
=======
What's on the gain side?
------------------------
The basic layout is the same: to start with, you get a class,
``fuse.Fuse``. You implement a filesystem by subclassing this class and
add the filesystem as class methods. You can mount your filesystem class
by calling the ``main()`` method of one of its instances. The list of
possible filesystem methods is available as ``fuse.Fuse._attrs``. See
the example filesystems to find out which arguments are passed to these.
There are two command lines in the game. One is the actual command line
which you have to deal with, the other is the FUSE command line: FUSE is
a command line driven library, the basic parameter for initializing the
library (apart from the method list) is an ``(argc, argv)`` pair.
So what's new? The new API has put emphasis on the following themes:
- FUSE is a command line driven library. Handling ``sys.argv`` and
the FUSE command line should be integrated.
- Object based interface to system structures.
- Wrap stateful I/O in OO.
- Add support for all FUSE features which are available at the level
the library is interfaced (the *high-level interface*).
- Reflection.
Let's see how these are implemented.
FUSE and the command line
-------------------------
Crudely there can be two ways to provide configuration hooks to some
C library:
- One is having a config structure as a part of the API, which is to be
filled and pass to some constructor or initializator when you start
interfacing with the library. Such an interface can be easily used
from C, but it is very fragile wrt. changing the config options.
- Other is to use some kind of markup or domain specific language
(*DSL*). This is flexible, but there should be provided a
parser/generator for this language to be possible to make use of it.
FUSE chose the latter way. Instead of using XML or some other widely
config format, FUSE made a simple decision: let the command line be our
DSL -- we have to grok the command line anyway. [#]_
So, there are two command lines in the game. One is the actual command
line (``sys.argv``), the other is the FUSE command line: the library can
be initialized with an ``(argc, argv)`` pair.
This makes the library user to want urgently:
- A way to easily generate a FUSE compatible command line from an abstract
spec.
- A way to easily extract such an abstract spec from the actual command line.
- A way to easily extract such an abstract spec from the actual command
line.
(... and these two procedures should interfere only via the spec.)
(... and these two procedures should interfere *only via the spec*.)
This is what's addressed by the new API.
The new API does this as follows:
- Now it's the Python code's duty to put together a complete FUSE command line
(in the form of a Python sequence). [#]_
- ``FuseArgs`` is the class for the abstract specification:
the ``FuseArgs#mountpoint``, ``FuseArgs#set_mod()``, and ``FuseArgs#add()``
attributes/methods enable you to set up such a beast; ``FuseArgs#assemble()``
dumps a complete FUSE command line.
- ``FuseArgs`` is the class for the abstract specification: the
``mountpoint``, ``set_mod()``, and ``add()`` attributes/methods enable
you to set up such a beast; ``assemble()`` dumps a complete FUSE
command line.
- ``Fuse`` got a ``parser`` attribute. It's an instance of ``FuseOptParse``,
which is derived from the ``OptionParser`` class of optparse_.
- ``Fuse`` got a ``parser`` attribute. It's an instance of
``FuseOptParse``, which is derived from the ``OptionParser`` class of
optparse_. [#]_
* ``FuseOptParse`` groks a new kind of option (a subclass of
``Option``), which has no short or long opts; it matches or not based on
its ``mountopt`` attribute, which is looked for among the comma-separated
members of a ``-o`` option.
``FuseOptParse`` groks a new kind of option (a subclass of
``Option``), which takes no short or long opts; it matches or not
based on its ``mountopt`` attribute, which is looked for among the
comma-separated members of a ``-o`` option.
* instead of a ``(values, restargs)`` tuple, ``#parse_args`` returns with a
``(values, restargs, fuse_args)`` triplet. The third member is an instance
of ``FuseArgs``, and it's filled with all those mount options which didn't
match any of your ``mountopt``-ish option specs (and with some other mount
related info, such as the mountpoint).
You can specify handlers these mountopts, just like to ordinary
options. The unhandled suboptions are collected in a ``FuseArgs``
instance.
- Calling ``Fuse#parse`` performs the parsing, and makes a note of the resulting
``FuseArgs`` instance. When you invoke ``Fuse#main``, the FUSE command line
will be inferred from this instance.
- Calling ``Fuse``'s ``parse()`` method performs the parsing, and makes
a note of the resulting ``FuseArgs`` instance. When you invoke
``Fuse``'s ``main()``, the FUSE command line will be inferred from
this instance.
- See *xmp.py* for a simple demonstration of all of this.
- Bonus: the ``Fuse.fuseoptref()``, ``FuseArgs#filter()`` functions give you
diagnostic tools for validating ``FuseArgs`` instances against the
capability of the underlying FUSE library (albeit probably you don't
want to use these, usually it's good enough if the library complains
if it gets something inappropriate).
.. [#] Originally this idea seemed as simple as there was no dedicated
parser/generator interface provided with the library. With FUSE 2.5 we
finally got the ``fuse_opt`` subAPI to make the command line more
accessible. That's for C programming, so we don't deal with it here.
.. _optparse: http://docs.python.org/lib/module-optparse.html
@ -138,3 +151,121 @@ This is what's addressed by the new API.
partially parsed pieces of the FUSE command line to the C code, which
used these directly in low level functions of the library, getting behind
the main commandline parsing routine of the FUSE lib with no real reason.
.. [#] To be precise, we have the ``SubbedOptParse`` subclass of
``OptionParser`` and ``FuseOptParse`` is further derived from
``SubbedOptParse``. ``SubbedOptParse`` is a generic class for
parsing and handling suboptions.
Simple objects to represent system structures
---------------------------------------------
In old Pythons, ``os.stat()`` returned file attributes as a tuple, and
for the convenient access of the stat values, you got a bunch of
constats with it (so you queried file size like
``os.stat("foofile")[stat.ST_SIZE]``). While this approach still works,
and if you print a stat result, it looks like a tuple, *it is, in fact,
not a tuple*. It's an object which is immutable and provides the
sequence protocol, just like tuple, but it has direct stat field
accessors. That is, you can do it now like
``os.stat("foofile").st_size``.
The same is the case with the FUSE bindings: for `getattr`, you are to
return an object which has attributes like those of an ``os.stat()``
result, and for `statfs`, you are to return an object which has
attributes like those of an ``os.statvfs()`` result. This, of course, can
be achieved by calling ``os.stat()``, resp. ``os.statvfs()`` and passing
on the result of this call. But you might feel like starting from
scratch. You can build on the ``fuse.Stat`` and ``fuse.StatVfs``
classes. Subclass and/or instantiate them and specify the stat/statvfs
attributes.
Similarly, when listing directories, you have to return a sequence of
``fuse.Direntry`` objects which can be constructed from filenames
(``fuse.Direntry("foofile")``).
Funnily, the above sentence contains *two lies*:
- *You don't necessarily have to return a sequence*. You just have to
return an object which implements the *iterator protocol*. In
practice, this means that you can *yield* the direntries one by one,
instead of aggregating them into a sequence.
- The direntries don't have to be instances of ``fuse.Direntry``, they
are just required to have some attributes. The ones other than
``name`` are probably not interesting for you. If you have large
directories, you might want to specify a unique ``offset`` value for
the direntries. This makes it possible for the system to read your dir
in several chunks, and in each turn, reading can be continued from
where it has been put off.
Filehandles can also be objects if you want
-------------------------------------------
The FUSE library (and the Python new API) supports stateful I/O. That
is, when you open a file, you can choose return an arbitrary object, a
so called *filehandle*. [#]_ FUSE internally will allocate a (FUSE)
filehandle upon open, and keep a record of your (Python) filehandle.
When the system will want to use the FUSE filehandle for I/O, the
respective Python method will get the (py-)filehandle as an argument.
Ie., you can use the filehandle to preserve a state.
You might as well want the filehandle to be an instance of a dedicated
class, and want the filesystem methods get delegated to the filehandle.
The new API can arrange this for you: set up a class, say ``Myfile``,
which implements the I/O related methods (`read`, `write`, ...), and set
``foose.file_class = Myfile`` before calling ``foose.main()`` (where
``foose`` is an instance of ``Fuse``). This will also imply that the
`open` fs method will be handled by instantiating ``Myfile``. Also note
that the *path* argument will be stripped upon delegation (except for
init time).
You can do the same for directories, too. Directory I/O methods have
similar names to file ones, just postfixed with `dir` (like `readdir`),
and there are not that many of them (there is no `writedir`). You can
register a directory class by setting the ``dir_class`` ``Fuse``
attribute. I bet you don't wanna use this feature, though.
.. [#] although it should not be an integer, as integers are treated as
error values
Complete support for hi-lib
---------------------------
The Python bindings support all highlevel (pathname based) methods of
the Fuse library as of API revision 26, including `create`, `access`,
`flush` and *extended attributes*.
Reflection
----------
In order to use the stateful I/O features as described above, the FUSE
library on your system has to be recent enough. It's very likely that it
will be so, as stateful I/O is around since a while, but if not... let's
try proactively prevent cryptic bug reports.
Therefore there is ``fuse.feature_assert()`` at your disposal. While
there are several possible features you can assert, the form you will
most likely use is ``feature_assert("stateful_files")``. This will raise
an exception if stateful I/O on files is not supported.
When it comes to reflection, we see that the command line based FUSE
config machinery is sadly unidirectional [#]_. There is no simple way
for querying the option list recognized by the lib. The best we have is
that we can dump a help message. The new Python API tries to make use of
this: it can mangle the help output into an instance of the
aforementioned ``FuseArgs`` class (``Fuse.fuseoptref()``). The most
convenient way to use this as follows: take a ``FuseArgs`` instance, eg.
as its yielded by parsing with ``FuseOptParse``, and call its
``filter()`` method. This returns a new ``FuseArgs`` with the *rejected*
options (which are not understood by the lib, according to the help
message), and also purges out these from self, so the remainder can be
safely passed down to FUSE.
.. [#] We can argue that it's not that said. We just pass on to FUSE
what we get from the user and it either eats it or blows up. Why
would we want more sophistication?