To support a particular file system (FS), a kernel module implementing a special interface (file_system_module_info defined in <fs_interface.h>) has to be provided. As for any other module the std_ops() hook is invoked with B_MODULE_INIT directly after the FS module has been loaded by the kernel, and with B_MODULE_UNINIT before it is unloaded, thus providing a simple mechanism for one-time module initializations. The same module is used for accessing any volume of that FS type.
There are several types of objects a FS module has to deal with directly or indirectly:
dev_t) to it and a handle (type void*) provided by the file system. The VFS creates an instance of struct fs_volume that stores these two, an operation vector (fs_volume_ops), and other volume related items. Whenever the FS is asked to perform an operation the fs_volume object is supplied, and whenever the FS requests a volume-related service from the kernel, it also has to pass the fs_volume object or, in some cases, just the volume ID. Normally the handle is a pointer to a data structure the FS allocates to associate data with the volume.ino_t) and, if in use, also with a handle (type void*). As for volumes the VFS creates an instance of a structure (fs_vnode) for each node in use, storing the FS's handle for the node and an operation vector (fs_vnode_ops). Unlike the volume ID the node ID is defined by the FS. It often has a meaning to the FS, e.g. file systems using inodes might choose the inode number corresponding to the node. As long as the volume is mounted and the node is known to the VFS, its node ID must not change. The node handle is again a pointer to a data structure allocated by the FS.A FS module has to (or can) provide quite a lot of hook functions. There are a few concepts that apply to several groups of them:
open*(), close*(), and free*_cookie(). The open*() hook is passed all that is needed to identify the object to be opened and, in some cases, additional parameters e.g. specifying a particular opening mode. The implementation is required to return a cookie (type void*), usually a pointer to a data structure the FS allocates. In some cases (e.g. when an iteration state is associated with the cookie) a new cookie must be allocated for each instance of opening the object. The cookie is passed to all hooks that operate on a thusly opened object. The close*() hook is invoked to signal that the cookie is to be closed. At this point the cookie might still be in use. Blocking FS hooks (e.g. blocking read/write operations) using the same cookie have to be unblocked. When the cookie stops being in use the free*_cookie() hook is called; it has to free the cookie.read_*() hook reads the next one or more entries into a struct dirent buffer. The rewind_*() hook resets the iteration state to the first entry.read*_stat() hook and must be written into a struct stat buffer.A vnode is the VFS representation of a node. As soon as an access to a node is requested, the VFS creates a corresponding vnode. The requesting entity gets a reference to the vnode for the time it works with the vnode and releases the reference when done. When the last reference to a vnode has been surrendered, the vnode is unused and the VFS can decide to destroy it (usually it is cached for a while longer).
When the VFS creates a vnode, it invokes the volume's get_vnode() hook to let it create the respective node handle (unless the FS requests the creation of the vnode explicitely by calling publish_vnode()). That's the only hook that specifies a node by ID; all other node-related hooks are defined in the respective node's operation vector and they are passed the respective fs_vnode object. When the VFS deletes the vnode, it invokes the nodes's put_vnode() hook or, if the node was marked removed, remove_vnode() .
There are only four FS hooks through which the VFS gains knowledge of the existence of a node. The first one is the mount() hook. It is supposed to call publish_vnode() for the root node of the volume and return its ID. The second one is the lookup() hook. Given a fs_vnode object of a directory and an entry name, it is supposed to call get_vnode() for the node the entry refers to and return the node ID. The remaining two hooks, read_dir() and read_query() , both return entries in a struct dirent structure, which also contains the ID of the node the entry refers to.
Which hooks a FS module should provide mainly depends on what functionality it features. E.g. a FS without support for attribute, indices, and/or queries can omit the respective hooks (i.e. set them to NULL in the module, fs_volume_ops, and fs_vnode_ops structure). Some hooks are mandatory, though. A minimal read-only FS module must implement:
struct stat info for the given node, consisting of the type and size of the node, its owner and access permissions, as well as certain access times.Although not strictly mandatory, a FS should additionally implement the following hooks:
While there is the access() hook that explicitly checks access permission for a node, it is not used by the VFS to check access permissions for the other hooks. This has two reasons: It could be cheaper for the FS to do that in the respective hook (at least it's not more expensive), and the FS can make sure that there are no race conditions between the check and the start of the operation for the hook. The downside is that in most hooks the FS has to check those permissions. It is possible to simplify things a bit, though:
open*() hook. E.g. in fs_vnode_ops::read() or fs_vnode_ops::write() one only has to check, if the file has been opened for reading/writing, not whether the current process has the respective permissions.One of the nice features of Haiku's API is an easy way to monitor directories or nodes for changes. That is one can register for watching a given node for certain modification events and will get a notification message whenever one of those events occurs. While other parts of the operating system do the actual notification message delivery, it is the responsibility of each file system to announce changes. It has to use the following functions to do that:
st_size member changes when the file is truncated or data have been written to it beyond its former size. The modification time (st_mtime) changes whenever a node is write-accessed. To avoid a flood of messages for small and frequent write operations on an open file the file system can limit the number of notifications and mark them with the B_WATCH_INTERIM_STAT flag. When closing a modified file a notification without that flag should be issued.If the file system supports queries, it needs to call the following functions to make live queries work:
The Haiku kernel provides three kinds of caches that can be used by a file system implementation to speed up file system operations: