updated docs
This commit is contained in:
parent
9a252a04d9
commit
fa051fba9b
4 changed files with 233 additions and 82 deletions
|
|
@ -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
24
INSTALL
|
|
@ -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
3
README
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
Loading…
Reference in a new issue