File Server Plugin Concepts

This topic describes the file server plug-in concepts.

File server plugins can issue direct file server requests, interpret and modify inbound messages and change file and metadata. A compression plugin may need to intercept file read or file write requests which are sent from the client to provide a seamless user experience. This operation requires the modification of the data, position and length arguments that are sent from the client to the file server.

There are some limitations to the file server plugin framework:

  • The file server plugin framework only operates on one client file at any time. However, a plugin itself can operate on any number of files during a single request from the client.

  • The file server plugin framework does not enable the compression or encryption of whole volumes. If access to the whole media volume is needed use a file server extension rather than a plugin.

  • File modification plugins cannot operate on demand paged executables. Demand paging does not use the file server, so the plugin framework cannot be used to encrypt/process the executable.

Note: The improved framework will not prevent deadlock with existing plugins. Plugins that currently issue RFs requests must be migrated to the new APIs to prevent deadlock. Follow the guidelines within the plugin implementation tutorial

Plugin type

There are two different types of file server plugins:

  • Observer plugins - intercept requests but do not modify file data or associated meta data.

    Examples of observer plugins are those for virus scanning or logging.

  • Modifier plugins - intercept requests and modify the data or associated meta data of the target files or directories..

    Examples of file modifier plugins are compression and encryption plugins.

Architecture

This section describes the structure of the plugin framework and the changes that have been made to the framework in v9.5:

The diagram below shows how plugins fit into the File Server software stack.

Note: more than one plugin can be loaded into the file server at the same time. A plugin is not aware of other plugins.

Threads and execution context

The Symbian platform File Server has multiple threads. There is a thread for each drive in use and a main thread that receives the requests from clients and sends them to the drive threads. Synchronous drives, however, do not have their own drive thread; requests for these are processed by the main thread. There is also a separate thread for processing session disconnect requests.

Each plugin also has its own thread for processing requests. Requests are dispatched to the plugin thread associated with the request's drive before they are dispatched to the drive thread. This is discussed in more detail in interception of file server requests.

Exclusive access

Previously, when a client application opens a file for exclusive write access, no other clients could access the file until the client closed its associated subsession. This also applied to plugins that needed to modify file data (as they are also clients of the file server).

Symbian platform provides plugins that are able to perform operations on files and directories irrespective of the mode in which the file has been opened.

Operations are now able to use the same file handle as the originating request (run in the same context as the original request) so files opened for exclusive access can still be written to by a plugin wishing to modify the file.

Intercepting ROM drive requests

Previously, plugins could not intercept any requests to the ROM drive (Z) however, there is a requirement to be able to intercept requests on this drive to enable secure-load and logging plugins. Requests to drive Z can now be intercepted by plugins. See the tutorial for CFsPluginFactory for more details.

Preventing deadlock

The new framework allows a plugin to have direct access to file data without the need to issue new file server requests through the RFile, RDir and RFs client APIs. In the pre-9.5 approach it was possible to reach deadlock if more than one plugin was present.

The diagram below shows two plugins in the pre v9.5 framework. If both plugins issue new file server requests and if the plugins are not re-entrant safe this can lead to deadlock in the file server.

Figure 1. File server deadlock

The framework introduced in v9.5 prevents deadlock by allowing plugins to issue internal file server requests after intercepting a request by using the newly introduced RFilePlugin, RDirPlugin and RFsPlugin APIs and not by using the RFs, RFile and RDir APIs. Internally issued requests are only dispatched to plugins mounted below the issuing plugin and the appropriate drive thread.

The sequence of events for a typical plugin intercepting a file read request is illustrated below:

Figure 2. Read request intercepted by a plugin

The classes RFilePlugin, RDirPlugin and RFsPlugin have been introduced to facilitate plugins with direct access behaviour on file and directory requests. These classes are analogous to the client-side RFile, RDir and RFs classes and have functions with similar or identical signitures.

In order to perform requests on a file it is necessary to open a sub-session by calling either AdoptFromClient() or Open(). AdoptFromClient() is used in order to open a sub-session with the file associated with the client’s request. Alternatively the Open() method can be used to open either a different file, or the file associated with the client’s request possibly with a different access mode.

More than one file can be opened at any time by creating multiple instances of RFilePlugin.

The following example shows how to open many files from a plugin during a single request:

// Define the object
RFilePlugin clientsFile(aRequest); 

// Opens the file associated with the intercepted request
Tint r = clientsFile.AdoptFromClient();
User::LeaveIfError(r);
TBuf<20> data;
TInt64 pos = (Tint64)0;
Tint length = 0;

// Read the file
r = clientsFile.Read(pos, data, length);
User::LeaveIfError(r);

// Open a second file
RFilePlugin secondFile(aRequest);
_LIT(KSecondName, ”D:\\myfile.txt”);
r = secondFile.Open(KSecondName(), EFileRead);
User::LeaveIfError(r);

// Read from second file
TBuf<20> data2;
TInt64 pos2 = (Tint64)0;
Tint length2 = 0;

// Read the file
r = secondFile.Read(pos2, data2, length2);
User::LeaveIfError(r);

// Close the files
clientsFile.Close();
secondFile.Close();

Issuing an internal file system request

An internal request can be marked as ‘Direct to Drive’. This allows requests that are generated by a plugin to be dispatched straight to the drive thread bypassing all other plugins which may be mounted below the plugin that issued the request. After being processed by the drive thread the request also bypasses the plugins between the drive thread and the plugin that the Direct to Drive request originated from. Once returned to the plugin that generated the request, the Direct to Drive request is complete and any other requests issued from the plugin are processed by plugins further down the plugin-stack in the normal manner.

The last argument of the RFilePlugin constructor of the classes RFilePlugin, RFsPlugin and RDirPlugin is named aDirectToDrive. aDirectToDrive is a boolean value (TBool) to indicate that the request is Direct to Drive. The default value is EFalse indicating that the request is interceptable by plugins further down the plugin-stack.

Plugin order

The Plugin framework provides support for multiple plugins to be present and active. Plugins are arranged in a stack. Plugins intercept requests in the order they are arranged in the stack with the plugin at the top of the stack (at the smallest numerical absolute position) getting the first intercept. The order of plugins in the stack is therefore crucial to the correct operation of the file server when more than one plugin is active, especially when the plugins are modifier plugins (i.e when both modify the data or parameters of the requests).

If two plugins are active and one of those is a virus scanner, for the virus scanner to operate correctly it must be the first plugin to intercept requests. This is so that the virus scanner can have first refusal to block any requests for files which it believes may not be safe for opening. If there is another plugin higher in the stack then this plugin could send Direct To Drive requests, for example, and would significantly reduce the virus scanning ability of the virus scanning plugin.

Two mechanisms are provided that allow plugin authors to control the position of their plugins in the plugin stack these are Absolute position and Unique position.

Absolute position

Plugins can be inserted into the stack by specifying an absolute position at mount time. This absolute position is the index in the internal array of plugins in which the plugin should be mounted. The plugin at position 0 being the first plugin to be able to intercept requests.

Figure 3. Plugin stack showing absolute positions

The absolute position method is more appropriate for mounting plugins that operate regardless of their position in the plugin stack or when all of the plugins for a device are known and installed when manufactured. This method does not suit plugins that are added after manufacture where the dependencies of other available plugins is not known. If this is the case use the unique position method described below.

Unique position

Plugins can be ordered according to a unique position stored within the plugin. Unique position identifiers are defined by the manufacturer during the software validation/signing process. Unique positions are defined in the derived CFsPluginFactory class. See the description in CFsPluginFactory.

The position value specifies the category and position of the plugin:

plugin Type

Unique Position Range

File Observers

0x20000000 - 0x2FFFFFFF

File Modifiers

0x40000000 - 0x4FFFFFFF

File Observers do not modify data so they are allocated a lower range of numbers placing them at the top of the plugin stack. File Modifiers modify the data stream so they are allocated a higher range placing them lower down the plugin stack.

Note: a plugin has a unique position then do not specify an absolute position when mounting it, otherwise the error KErrNotSupported is returned.

Drive selection

A File Server Plugin can intercept requests for a specific drive or for all drives. A plugin that intercepts requests for all drives must filter requests that are not appropriate for some drives. Requests can be filtered within an overridden CFsPlugin::Deliver() function.

There are two ways to specify a drive:

If the drive is not specified then RFs::MountPlugin() attempts to mount the plugin for all drives. If this behaviour is not supported by the plugin KErrNotSupported is returned.

Interception of file server requests

After a file server request has been initialised by the main file server thread it can be intercepted by a plugin. There are two types of intercept:

Plugins can register for pre-intercepts, for post-intercepts or for both pre and post-intercepts.

Pre-operation

Pre-operation intercepts occur before the drive associated with a request processes it. In a file write request for example, the pre-intercept operations occur before data has been written to the file. Requests are passed down the plugin-stack from the file server towards the drive thread.

When the main file server thread has initialised a request, the request is dispatched to the highest plugin in the stack. This plugin must have been mounted on the requested drive and registered to pre-intercept this type of request. See mounting a plugin. The request is dispatched by calling the plugin's CFsPlugin::Deliver() function.

The Deliver() function runs in the context of the previous calling thread, this can be the main file server thread or a plugin thread. Override CFsPlugin::Deliver() to filter the request. If the Deliver() function has not been overridden then the request is dispatched for asynchronous processing by calling the base class Deliver(). See the CFsPlugin::Deliver() tutorial section.

Post operation

Post-operation intercepts occur after the drive associated with a request has processed it. In a file write request for example, the post-intercept occurs after data has been written to the file. Requests are passed from the drive thread back up the stack towards the client.

When the drive thread has finished processing a request it dispatches it to the plugin lowest in the stack by calling the plugin's Deliver() function.

Filtering requests internally

The CFSPlugin::Deliver() function filters the request and decides what kind of action is neccessary in terms of the flow of the request through the plugin stack:

  • KPluginMessageForward is returned if the intercept is pre-operation. The request is passed to the next plugin down the stack or to the drive thread if there are no more plugins.

  • KPluginMessageComplete is returned if the intercept is post-operation. The request is passed to the next plugin up the stack or if there are no more plugins to process the request it is passed to the main file server thread.

If the request requires processing by the plugin then the plugin's Deliver() function calls the base class Deliver() function and the request is dispatched to the plugin's thread for asynchronous processing.

Asynchronous processing is carried out in the plugin's DoRequestL() function. A plugin can only have a single DoRequestL() function which must handle both pre and post-intercepts. Plugin authors can use the IsPostOperation() function of the utility class, TFsPluginRequest to indicate whether the DoRequestL() is processing a request as a pre-intercept or post-intercept. See the description of TFsPluginRequest.

Once the asynchronous processing is complete the DoRequestL() function returns KErrNone and the request is passed to the next plugin down the stack by calling its Deliver() function. If there are no lower plugins then the request is passed to the appropriate drive thread for processing.

If the plugin intercepts a request in pre-operation and wants to complete the request on behalf of the client then the plugin can return KErrCompletion to indicate that the request has been completed and that the request is now in post operation mode. The flow will then proceed to any previous plugins if mounted or directly back to the client otherwise. KErrCompletion prevents any further plugins further down the stack from intercepting the request.

When a plugin intercepts file read or file write and does an early completion (i.e. returns KErrCompletion in pre-intercept) then the plugin author should call TFsPluginRequest::SetSharePos() to allow share position to be updated after early read/write completion.

Security

File server plugins are implemented as libraries that are loaded into the file server process at runtime. Therefore, plugins must have the same platform security capabilities as the file server process, these are TCB, ProtServ, DiskAdmin, AllFiles, PowerMgmt and CommDD.

Any user side process that wishes to load and mount plugins must have the DiskAdmin capability.