Within the filesystem server library, a number of different abstractions and mechanisms are employed to provide access to filesystem objects.
The server library is provided by libfsserver within the departure package.
This document uses C-style interface syntax since the components operating within the described mechanisms will themselves be described using such syntax.
The Accountable interface provides the following operations:
void attach(); unsigned int detach();
Its purpose is to provide reference counting for components, with the attach operation incrementing the number of users of a component, and with the detach operation decrementing the number of users and returning the resulting number of users. When components no longer have any attached users, they may perform operations to tidy up after themselves and permit their deallocation.
The Provider abstraction employs this interface to record its usage by multiple resources.
Accessors provide the means of accessing filesystem object data and metadata. Conceptually, files and directories are both exposed by accessors, although the interfaces and mechanisms may differ between these types of objects.
Currently, directory accessors provide support for traversing directory listings, obtaining the relevant filesystem metadata using the underlying filesystem access library.
The DirectoryAccessor interface provides the following operation:
void read_directory(file_t *writer);
The read_directory operation is presented with a writer object into which directory listing data will be written. In the case of the ext2 directory accessor, the writer is presented to a directory iterator which traverses the directory data structure, invoking a callback sending directory entries via the writer to the client.
File content is accessed through the Accessor interface with the following operations:
void close(); void fill(Flexpage *flexpage); void flush(Flexpage *flexpage); offset_t get_size(); void set_size(offset_t size);
The operations need to be supported with actual filesystem operations. For example, ext2-based filesystems employ a specific abstraction which invokes library functions provided by libext2fs.
Providers encapsulate the essential functionality for accessing filesystem objects. Implementing the Accountable interface, they are shared by resources and discarded when no resources are using them.
The following operations are supported by providers as defined by the Provider interface:
ProviderRegistry *registry(); long make_resource(offset_t *size, object_flags_t *object_flags, Resource **resource); bool removal_pending(); void remove_pending(bool remove);
A provider is created to represent a filesystem object when an attempt is made to open that object. It is then recorded in a provider registry so that subsequent attempts to open the object yield a common provider instance. The provider registry having ownership of each provider can be obtained using its registry operation.
See the file opening mechanism for details of the creation and registration of providers.
Providers also support the creation of resources through which each user of a provider exercises its use of that provider. The make_resource operation performs the creation and returns size, flags and resource instance details.
See the file opening mechanism for details of resource creation.
Deallocation of providers is managed by the provider registry when resources are closed and detached from providers. Since the registry effectively has ownership of the provider, having registered it, the registry must therefore be involved in its deallocation, deregistering it first.
The removal of filesystem objects that are being represented by providers can be directed using the remove_pending operation. Where remove_pending has been called with a true value as its parameter, the removal_pending operation will also return a true value, and upon the provider being discarded, a removal operation will be invoked on the underlying object being provided.
See the file removal mechanism for more details of the invocations involved.
Resources are objects accessed by clients that support a basic level of accounting and management. They act as servers and receive messages via the interprocess communication (IPC) mechanism.
The Resource abstraction is intended to work with lower-level mechanisms provided by libipc involving data structures and functions that are usable in the C programming language. This abstraction integrates the fundamental libipc support with C++.
The generic operations of the Resource interface are as follows:
void activate(); void close();
The activation of a resource, supported by activate, is an optional operation that performs any initialisation before a resource is made available as a server.
The close operation is invoked when resources are to be discarded.
See the file closing mechanism for the context in which the close operation is invoked.
In practice, other operations are required to make resources useful. Such other operations are provided by classes inheriting from Resource and thus specialising it. Such classes also inherit from IPC interface types so as to be able to support the invocation of operations exposed by such interfaces.
In some cases, resources provide the mechanism by which each user of a filesystem object may access that object independently. They would then effectively provide a session in which accesses can occur.
Directory resources primarily expose the contents of directories in the filesystem to a user. They employ directory accessors which concern themselves with the actual filesystem content.
Directory components are provided using directory resources.
Pagers are resources that support dataspace access operations, thus allowing the resources to expose filesystem content in mapped memory regions to a particular user of the object providing the content.
File components and pipe components are provided using pagers.
Filesystem resources provide the entry point for access to a filesystem by other components or programs. Since filesystems often enforce identity-based access controls, a filesystem resource will typically support the open_for_user operation in various forms, with the result of this operation being the instantiation of an OpenerResource configured for the indicated user identity.
Resources must also support specific operations for integration with the lower-level IPC handling implemented in libipc. The following operations will be invoked by the server framework in libfsserver to configure a server controlled by libipc:
ipc_server_default_config_type config(); void *interface();
The config operation returns a default configuration object containing details pertinent to the initialisation of a server for the resource, these involving aspects of incoming messages and the handler function to deal with such messages. Typically, such an object, along with the handler function, will be generated by a tool such as idl.
The interface operation returns a pointer to the resource instance coerced to an appropriate type. Such a type will be that of the most specialised IPC interface type inherited by the resource implementation, as expected by the handler function for the resource.
For example, consider the following:
class FilePager : public Pager, public MappedFileObject
Here, the interface operation will return a pointer coerced to MappedFileObject, and the config operation will return a suitable configuration object. The idl tool will produce an object called config_MappedFileObject for this purpose along with a handler function called handle_MappedFileObject available via the configuration object.
The basic mechanism for obtaining a resource involves a registry, as illustrated by the following diagram.
The ResourceRegistry coordinates access to filesystem resources and, through synchronisation, prevents conflicting operations from occurring concurrently. For example, a removal operation on a file may not be allowed to occur while an opening operation is in progress.
To achieve this, a common lock is employed to serialise access, with any underlying filesystem operations being initiated from the registry while the lock is held. An object providing the FileOpening interface for a filesystem provides such operations, and the following interaction pattern is thereby employed:
Since the ResourceRegistry functionality is generic, it could be specialised for each filesystem or be configured with an appropriate reference to a FileOpening object. The OpenerResource would then be generic, invoking the registry which would, in turn, invoke the opening object.
However, the chosen approach is to permit OpenerResource objects to implement the FileOpening interface for each filesystem, meaning that the ResourceRegistry will end up being called by the opener and then invoking the opener in return. This is slightly more convenient than the alternative since the opener can be configured with a given user identity, and such identity details will ultimately be employed when accessing the underlying filesystem itself.