summaryrefslogtreecommitdiff
path: root/hurd
diff options
context:
space:
mode:
Diffstat (limited to 'hurd')
-rw-r--r--hurd/libnetfs.mdwn297
1 files changed, 297 insertions, 0 deletions
diff --git a/hurd/libnetfs.mdwn b/hurd/libnetfs.mdwn
new file mode 100644
index 00000000..41f39521
--- /dev/null
+++ b/hurd/libnetfs.mdwn
@@ -0,0 +1,297 @@
+[[meta copyright="Copyright © 2007, 2008 Free Software Foundation, Inc."]]
+
+[[meta license="""[[toggle id="license" text="GFDL 1.2+"]][[toggleable
+id="license" text="Permission is granted to copy, distribute and/or modify this
+document under the terms of the GNU Free Documentation License, Version 1.2 or
+any later version published by the Free Software Foundation; with no Invariant
+Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license
+is included in the section entitled
+[[GNU_Free_Documentation_License|/fdl]]."]]"""]]
+
+#libnetfs
+
+WHAT IS THIS
+
+This document is an attempt at a short overview of the main concepts
+used in the process of development of translators using
+*libnetfs*. You will **not** find here a detailed description of the
+required callbacks (for this take a look at
+<http://www.debian.org/ports/hurd/reference-manual/hurd.html>). You
+will **not** find a complete example of code either (usually,
+*unionfs* is suggested as an example)
+
+WHAT IS LIBNETFS
+
+*libnetfs* is a Hurd library used in writing translators providing
+some virtual directory structures. For example, if you would like to
+create a translator which shows a *.tar* archive in a unpacked way,
+you will definitely want to use *libnetfs*. However, it is important
+to understand one thing: real filesystem servers (like *ext3* and
+such) do **not** use *libnetfs*, instead, they rely on *libdiskfs*,
+which is, generally speaking, seriously different from *libnetfs*.
+
+All in all, *libnetfs* is the library you would choose when you want
+to write a translator which will show a file (or a directory) in a
+modified way (for example, if you'd like to show only *.sh* files or
+make an archive look unpacked). As different from *libtrivfs*, using
+*libnetfs*, you can show to your clients not just a single file, but a
+whole directory tree.
+
+HOW IT WORKS: SHORT DESCRIPTION
+
+With the aid of *libnetfs* a translator (supposedly) publishes a
+directory tree. All lookups in this directory tree are directed to the
+translator and the latter is free to provide whatever (consistent)
+information as the result of the lookup. Of course, all other usual
+requests like reading, writing, setting a translator, etc. are
+directed to the translator, too. The translator has either to
+implement the required functionality in the corresponding callback or
+just return an appropriate error code (for example, EOPNOTSUPP), if
+the callback is compulsory.
+
+THE MAIN CONCEPTS: NODES
+
+The most fundamental thing to understand about *libnetfs* is the
+notion of a **node**. Nearly always there are two types of nodes in a
+*libnetfs*-based translator:
+
+* Generic **node**, defined in *<hurd/netfs.h>*. This node contains
+ information read and written by the programmer (like field
+ *nn_stat*), as well as some internal information (like fields
+ *references* and *transbox*). Of course, the programmer is free to
+ use these fields at will, but they should know what they are doing.
+
+* Custom **netnode**, defined by the programmer and containing only
+ the information valuable for the programmer, but not for *libnetfs*.
+
+The generic node is probably the most important primitive introduced
+by *libnetfs*. Callbacks receive the nodes they should work with as
+parameters; some of them return nodes as the result of their
+operation. To some extent of certainty, a *libnetfs* node can be
+perceived similarly to a filesystem node -- the building-brick out of
+which everything is composed.
+
+As it can be seen from the definition in *<hurd/netfs.h>*, a reference
+to a netnode is stored in each generic node. In a way, a netnode can
+be perceived as the custom attachment to the information contained in
+a generic node. The link between these is quite strong. At first this
+might not look like a very important thing, but let's analyze a simple
+example: you would like to show the contents of a directory in a
+filtered way. As a filtering criterion you would like to use the
+result of the execution of a command specified as a command line
+argument to the translator. If a client looks up a 'file' in the
+directory tree provided by the translator, the latter should feed the
+name of the file to the filtering command and decide whether to hide
+this file or not upon receiving the result.
+
+To avoid trouble, the translator had better use the *absolute* name of
+'file'. Obviously, the translator would like to organize all of the
+nodes in a hierarchy. To make things work more or less fast, it is a
+reasonable decision to construct the absolute path to a node at
+creation and store it inside the netnode (which, in turn, is inside
+the node). However, such an approach is not a good one when using
+*libnetfs*. Generally speaking, a *new* generic node is created at
+each lookup, and, together with it, a new netnode is constructed. The
+conclusion is that a *libnetfs* node is a rather transient phenomenon,
+and when we want to store some information which is relatively
+expensive to obtain, we need something more than a generic node +
+netnode. At this moment most of the translators (like *unionfs*,
+*ftpfs*, etc.) introduce the concept of a **light node**.
+
+A **light node** is a user-defined node which contains some
+information expensive to obtain, which had better not be stored
+directly in a netnode. All netnodes, contained in generic nodes which
+resulted in lookups of the same file, share references (pointers,
+actually) to a single light node. Light nodes are created when the
+first attempt to lookup a file is done, and they are destroyed when no
+netnodes reference them. It is very important to understand that
+*libnetfs* does **not** enforce the programmer to define light
+nodes. Everything can be stored within netnodes inside generic
+nodes. Light nodes are just a matter of organizing data in an
+efficient way.
+
+Probably, you are already thinking ``Why cannot *netnodes* be shared?
+Why do we need yet another notion?''. The answer is that the link
+between a netnode and a *libnetfs* node should be one-to-one, because
+netnodes usually store information specific of *only one* node, whilst
+light nodes contain information common to several nodes. If one chose
+to share netnodes, one would not be able to store additional
+information per a *libnetfs* node, and this is quite a serious problem
+in most practical problems.
+
+WHY A LIBNETFS NODE IS NOT QUITE A FILESYSTEM NODE
+
+The most demonstrative argument in this case is the definition of
+*struct node* in *<hurd/netfs.h>*. If you try to find in this
+definition some reference to other generic node called *parent*, or an
+array of references called *children* (which would be quite classical
+for a member of a hierarchy), you will fail. There are fields *next*
+and *prevp*, but these are for internal use and only include the node
+in an internally maintained *list*, not a tree. Surprisingly enough,
+*libnetfs* does **not** manage the tree-like structure for you. You
+have to do that *on you own*. This is another moment when light nodes
+come triumphantly to light. Most *libnetfs*-based translators organize
+their light nodes in the tree-like structure reflecting the directory
+tree shown to the user. When a lookup is performed, a light node is
+either created or reused (if it has already been created in a previous
+lookup). The result of the lookup is a *libnetfs* node created basing
+on the information contained in the found light node.
+
+From the point of view of a *libnetfs* programmer, light nodes are the
+conceptual filesystem nodes. A translator knows who is the parent of
+who *only* from studying the links between light nodes. And a light
+node does contain a reference to its parent and an array of references
+to children. When a translator is asked to fetch a file, it finds this
+file in the tree of light nodes firstly, creates a *libnetfs* node
+based on the found light node, and returns the latter as the
+result. Therefore, it is not quite right to perceive *libnetfs* nodes
+as filesystem nodes. Instead, the focus of attention should stay upon
+light nodes.
+
+HOW IT WORKS: A MORE VERBOSE DESCRIPTION
+
+At first let us see how the a *libnetfs*-based translator responds to
+lookup requests. At the beginning the *netfs_attempt_lookup* callback
+is called. It knows the generic *libnetfs* node corresponding to the
+directory under which the lookup shall take place, the name it has to
+lookup, and the information about the user requesting the lookup. This
+callback is supposed to create a new *libnetfs* node corresponding to
+the requested file or return an error. As it has been said before,
+usually translators browse their hierarchy of light nodes to know
+whether a file exists within a directory or not. Note that
+*netfs_attempt_lookup* does not know the flags with which a
+*file_name_lookup* call is done, what it has to do is just to provide
+a new node or return an error.
+
+Then *netfs_validate_stat* callback is called and a node and
+information about the user is passed inside. This callback is a rather
+simple one: it has to assure that the *nn_stat* field of the supplied
+node is valid and up to date. Translators which mirror parts of real
+filesystem, like *unionfs*, usually treat the node corresponding to the
+root of their node hierarchy in a specific way. The reason is that the
+root node is not a mirror of a real file -- it is almost always a
+directory in translators of this kind.
+
+The third stage is an invocation of
+*netfs_check_open_permissions*. This callback is, probably, one of the
+simplest in most cases. It knows some information about the user
+requesting the open, about the node that is about to be returned to
+the user, and about the flags supplied by the user in the call to
+*file_name_lookup*. Besides that, this callback is provided with the
+information whether the requested lookup ended in creating a new file
+or whether the requested file already
+existed. *netfs_check_open_permissions* has to decide if the user has
+the right to access the resulting file under the permissions specified
+in flags. It has to return either 0 or the corresponding error.
+
+These are the most basic steps of the lookup. Note that if the file
+was requested with O_CREAT flag and *netfs_attempt_lookup* could not
+locate this file, *netfs_attempt_create_file* is called. In many ways
+a typical implementation of this callback might be similar to the
+implementation of *netfs_attempt_lookup*. However,
+*netfs_attempt_create_file* will most probably have to do less checks.
+
+Let's move to listing the contents of a directory. The corresponding
+callback, *netfs_get_dirents* is triggered when a user invokes
+*dir_readdir* upon a directory provided by the translator. The
+parameters of *netfs_get_dirents* are therefore very similar to the
+parameters of *dir_readdir*. Actually, translator *fakeroot* only
+calls *dir_readdir* in this callback and nothing more. In translators
+which need more complex handling (like filtering the contents) the
+code of this is more sophisticated. Sometimes the listing of directory
+entries happens in several stages: *netfs_get_dirents* may call
+something like *node_entries_get*, and the latter may invoke
+*dir_entries_get*. The latter function calls *dir_readdir* and
+converts the result to an array of *struct dirent*
+'s. *node_entries_get* converts the array of *struct dirent* 's to a
+linked list and decides whether a specific file shall be included in
+the result or not. Finally, *netfs_get_dirents* converts the linked
+list provided by *node_entries_get* to the format of the result of
+*dir_readdir* and returns the converted data to the user. The
+described stages are the stages of listing directory entries in
+*unionfs*, for instance.
+
+Other callbacks are, generally speaking, less sophisticated. For
+example, when the client wants to read (write) from a node provided
+by *netfs_attempt_lookup*, the callback *netfs_attempt_read*
+(*netfs_attempt_write*) is triggered. Both callbacks have sets of
+parameters to the corresponding *io_read* and *io_write* functions.
+
+While browsing the code of very many *libnetfs*-based translators, you
+might notice that they define callbacks starting with
+*netfs_S_*. Usually a name similar to that of one of the file
+management function follows (like netfs_S_*dir_lookup). These
+callbacks are triggered when the corresponding functions are called on
+files shown by the translator. Such translators override parts of the
+core functionality provided by *libnetfs* to achieve better
+performance or to solve specific problems.
+
+SYNCHRONIZATION IS CRUCIAL
+
+A *libnetfs* programmer shall always keep in mind that, as different
+from *libtrivfs*-based translators, *libnetfs*-based translators are
+always multithreaded. To guard data against damage each node
+incorporates a lock. Moreover, each light node usually contains a
+lock, too. This happens because *libnetfs* nodes and light nodes are
+loosely coupled and are often processed separately.
+
+NODE CACHE
+
+Most of *libnetfs* translators organize a *node cache*. However, this
+structure is not a real cache. The idea is to hold some control over
+life and death of *libnetfs* nodes. The cache is usually a
+doubly-linked list: each netnode contains a reference to the previous
+node in the cache and a reference to the next one. When a new node is
+created (for example, as a result of invocation of
+*netfs_attempt_lookup*), it is registered in the cache and its number
+of references is increased. It means that, by putting the node in the
+cache, the translators gets hold of an extra reference to the
+node. When in subsequent lookups the same nodes will be requested, the
+translator can just reuse an already existing node.
+
+Of course, the cache is limited in size. When the cache gets
+overgrown, the nodes located at the tail of the list are removed from
+the cache and the references to them are dropped. This triggers their
+destruction (undertaken by *libnetfs*).
+
+WHAT FILES ARE USUALLY CREATED
+
+If you take into a look at the sources *ftpfs* or *unionfs* you will
+notice files with names similar to the following:
+
+* cache.{c,h} -- here the node implementation of the node cache
+ resides.
+
+* lib.{c,h}, dir.{c,h}, fs.{c,h} -- these contain the implementation
+ of some internals. For example, the function *dir_entriesget*
+ mentioned in the description of the process of listing directory
+ entries, will most probably reside in one of these files.
+
+* options.{c,h} -- here the option parsing mechanism is usually
+ placed. Argp parsers are implemented here.
+
+* <*translator_name*>.{c,h}, netfs.c -- the implementation of *netfs_\**
+ callbacks will most probably lie in these files.
+
+WHAT NETNODES AND LIGHT NODES USUALLY CONTAIN
+
+A **netnode** usually contains a reference to a light node, some flags
+describing the state of the associated generic *libnetfs* node, and
+the references to the previous and the next elements in the node
+cache.
+
+A **light node** usually contains the name of the file associated with
+this light node, the length of this name, some flags describing the
+state of this light node. To make a light node fully usable in a
+multithreaded program, a lock and a reference counter are almost
+always incorporated in it. Since light nodes are organized in a
+hierarchical way, they contain a reference to their parent, a
+reference to their first child, and references to their siblings
+(usually not very descriptively called *next* and *prevp*).
+
+THE END
+
+I very much hope this piece of text was at least a little
+helpful. Here I tried to explain the things which I understood least
+when I started learning *libnetfs* and which confused me most. Feel
+free to complete this introduction :-)