File Server Plugin implementation tutorial

This document contains guidelines for writing a file server plugin for Symbian platform.

This document is split into three sections:

Writing a plug-in

A file server plug-in is made up of at least two classes:

Each plug-in must register to intercept messages. To make internal requests call the functions RFilePlugin, RDirPlugin or RFsPlugin.

Each plug-in DLL must export a plug-in entry point function with the prototype:

CFsPluginFactory* CreateFileSystem();

The loader calls this function when a client wishes to install a file server plug-in and creates an instance of the factory class. For example:

extern "C" {

EXPORT_C CFsPluginFactory* CreateFileSystem()
    {
    return(new CMyPluginFactory());
    }
} 

CFsPluginFactory

The CFsPluginFactory base class follows the standard factory pattern as used in other file server DLLs. CFsPluginFactory creates CFsPlugin derived objects.

plug-in authors must provide implementations of the pure virtual functions:

  • Install()

    This function is called by the file server in response to a call to RFs::AddPlugin() and installs the plug-in factory.

    This function must set the name of the plug-in factory with a call to CObject::SetName(). The plug-in name is used when mounting, dismounting and unloading plug-ins. Optionally, RFs::AddPlugin() can perform other plugin specific initialization, for example setting iSupportedDrives to indicate which drives the plugin will operate on, as required by the plugin factory. See drive selection.

  • NewPluginL()

    This is called by the file server in response to a call to RFs::MountPlugin(), this function creates a new CFsPlugin derived object and returns a pointer to it.

  • UniquePosition()

    This function returns the unique ID of the plugin. This is used to identify the plugin and to specify the location of the plugin in the chain. See unique position.

  • Remove() (overriding this virtual function is optional as it is not a pure virtual function)

    This is called just before the plugin factory object is destroyed and allows any clean up to be carried out.

    The default implementation just returns KErrNone. Implementations should return an error code on error detection.

The CFsPluginfactory base class also has the following member variables:

  • iSupportedDrives

    This member holds a bit mask that indicates which drives the plugin created by this factory supports being mounted on. Bits 0 to 25 correspond to 26 drive letters lettered from A to Z.

    Plugin authors should use the the SetSupportedDrives() API in order to correctly set up which drives their plugin should be mounted on. Set the drive number to KPluginSupportAllDrives to indicate that the plugin needs to be mounted on all drives.

    If you are using Symbian platform prior to version 9.5, iSupportedDrives is assigned to by plugin writers directly. However, this is now discouraged in favour of using SetSupportedDrives.

  • iUniquePos

    This member stores the unique position identifier. If this member is used then plugin authors should implement the UniquePosition() function to return the variable.

An example skeleton implementation of a CFsPluginFactory derived class is as follows:

class CMyPluginFactory : public CFsPluginFactory
    {
public:
    CMyPluginFactory();
    virtual TInt Install();            
    virtual CFsPlugin* NewPluginL();
    virtual TInt UniquePosition();
    virtual TInt Remove();
    };

CMyPluginFactory::CMyPluginFactory()
    {
    // Constructor for the plugin factory
    }

TInt CMyPluginFactory::Install()
    {
    // Install function for the plugin factory
    iUniquePos = 0x2000001;
    // Mount on all drives
    SetSupportedDrives(KPluginSupportAllDrives);
    return SetName(_L("MyPluginName"));
    }

CFsPlugin* CMyPluginFactory::NewPluginL()
    {
    // plugin factory function, creates the plugin
    return CMyPlugin::NewL();
    }

TInt CMyPluginFactory::UniquePosition()
    {
    // Return’s the unique position identifier for plugins created by this factory class.
    return iUniquePos;
    }

TInt CMyPluginFactory::Remove()
    {
    // Clean up function for the plugin factory
    return KErrNone;
    }

CFsPlugin

This is the base class for the file server plugin and is defined in f32plugin.h.

Plugins must register to intercept particular types of file server requests. See Registering a plugin to intercept messages.

Plugin authors need to provide an implementation of the pure virtual method DoRequestL() and can optionally override the virtual functions:

SessionDisconnect()

SessionDisconnect() is called by the file server when a file server session is disconnected.

The default implementation just returns KErrNone. Overriding this function allows plugins to free up any resources prior to the outstanding operations being cancelled.

InitialiseL()

The default implementation of InitialiseL() does nothing. Override this implementation to perform a plugin specific initialisation, for example registering intercepts. If the plugin cannot be initialised this function must leave with a suitable error code.

Deliver()

Deliver() is called in the context of the previous thread that originated or processed the request (either the file server main thread or another plugin thread) before the request is dispatched to the plugin’s thread.

The default implementation delivers the request to the end of the plugin thread's request queue. Requests that require priority handling (usually those sent using an RPlugin session) are delivered to the front of the queue. See Communicating with plugins.

Overriding this function allows plugins to perform operations within the context of the original thread (like validation of request parameters or filtering requests) before the request is sent to the plugin’s main thread or passed to the next plugin or drive thread.

Deliver() is called from outside the context of the plugin’s main thread so care must be taken as the plugin main thread may still be processing the previous request.

  • If KErrNone is returned then the base class implementation of this function must be called to ensure that the request is dispatched to the plugin’s thread for further processing in DoRequestL().

  • KPluginMessageForward indicates that the request has been processed synchronously and should be passed down to the next plugin in the stack (bypassing the current plugin’s DoRequestL() function). This is used for pre-intercepts.

  • KPluginMessageComplete indicates that the request has been processed synchronously and should be passed up to the previous plugin in the stack (bypassing the current plugin’s DoRequestL() function). This is used for post-intercepts.

NewPluginConnL()

The function NewPluginConnL() is the default implementation and it returns KErrNotSupported. Override this implementation to create a CFsPluginConn derived object to enable direct communication between a client application and a plugin using RPlugin. See Communicating with plugins.

DoRequestL()

DoRequestL() is the main entry point for messages intercepted by a plugin. Individual plugins are usually single-threaded and must process requests in the order that they arrive. However, the plugin framework is not single-threaded. While a plugin is handling a request all other plugins are also able to handle requests. The drive thread is also able to handle requests forwarded to it by plugins. DoRequestL() is called in the context of the plugin’s main thread and must be implemented to intercept file server requests in this context.

The current request must have finished all processing before DoRequestL() has completed. If a plugin has multiple threads and the request is handled in a thread other than the main thread then the DoRequestL() function must be blocked until the request has been processed. This is because when DoRequestL() exits, the plugin framework forwards the message to the next plugin in the chain. Unpredictable behaviour may occur if the plugin has not finished processing a request before it is passed to the next plugin/file system.

Errors returned by this function are propagated back to the client. If this function leaves then the client thread panics. Note: Do not return from this function until the request has been fully processed. This is because after returning from this function the request is passed onto the next plugin in the chain. This means that you cannot pass the request to a separate thread and return immediately in order to implement a multi-threaded plugin.

KErrCompletion indicates that all processing for this request has been completed by this plugin. Post-intercept is then enabled and the flow of execution is up the plugin stack towards the client, starting at the previous plugin.

When a plugin intercepts a file read or file write request 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.

TFsPluginRequest

The TFsPluginRequest class encapsulates the intercepted file server request and is passed as a reference to the plugin's DoRequestL() function. TFsPluginRequest provides APIs that extract request specific information such as:

  • DriveNumber() - returns the target drive number. The drive number is in the range of zero to 25, which corresponds to drive letters A to Z,

  • Function() - the type of request as defined in TFsMessage,

  • IsPostOperation() - returns ETrue or EFalse to indicate if the request being intercepted is in post-intercept mode (ETrue) or pre-intercept.

TFsPluginRequest acts as a utility class for plugins. This class has two main functions TFsPluginRequest::Read() and TFsPluginRequest::Write(), which allow a plugin to read from and write to the message arguments of a request.

TFsPluginRequest::Read() has various overloads for TInt, TUint, Tint64 and descriptor data types, all of which take as their first argument a TF32ArgType object.

The TF32ArgType is an enumeration which defines the type of data that is requested. For example, EPosition, ELength, EData for getting the position, length and data arguments from the request. TF32ArgType is defined as follows:

enum TF32ArgType
    {
    EPosition,
    ELength,
    EData,
    ESize,
    EName,
    ENewName,
    EEntry,
    ETime,
    ESetAtt,
    EClearAtt,
    EMode,
    EAtt,
    EAttMask,
    EUid,
    EEntryArray,
    ENewPosition,
    };

TFsPluginRequest::Write() only has two overloads, both of these take descriptor types and the argument TF32ArgType. Many functions do not use descriptor types, it will be necessary to package data for these inside a descriptor.

The following provides an example of how to do this for Entry:

    TPckgC<TEntry> entryPckg(entry);
    err = aRequest.Write(EEntry, entryPckg);

Sending internal requests to the file server

The classes RFilePlugin, RDirPlugin and RFsPlugin, defined in F32Plugin.h, are the classes that plugin authors should use to send internal requests to the file server. The APIs for these classes are mostly identical from their client side (RFile, RDir and RFs) counter-parts.

Before a plugin can perform any requests on a file it must first either perform an Open() or AdoptFromClient(). When an AdoptFromClient() is performed the RFilePlugin instance opens the file associated with the client request. Open() can be used to open the same file (potentially with different parameters than those supplied by the client) and it can be used to open an entirely different file.

RFilePlugin has the following public functions:

class RFilePlugin : private RFile
    {
public:
    IMPORT_C RFilePlugin(TFsPluginRequest& aRequest, TBool aDirectToDrive = EFalse);

    // open a NEW file using same session as passed request
    IMPORT_C TInt Open(const TDesC& aName,TUint aMode);
    IMPORT_C TInt Create(const TDesC& aName,TUint aFileMode);
    IMPORT_C TInt Replace(const TDesC& aName,TUint aFileMode);
    IMPORT_C TInt Temp(const TDesC& aPath,TFileName& aName,TUint aFileMode);

    // re-open SAME file as client's request
    IMPORT_C TInt AdoptFromClient();
    // Transfer the plugin's open file to the client
    IMPORT_C TInt TransferToClient();

    IMPORT_C void Close();

    // RFile overloads
    IMPORT_C TInt Read(TInt64 aPos,TDes8& aDes) const;
    IMPORT_C TInt Read(TInt64 aPos,TDes8& aDes,TInt aLength) const;
    IMPORT_C TInt Write(TInt64 aPos,const TDesC8& aDes);
    IMPORT_C TInt Write(TInt64 aPos,const TDesC8& aDes,TInt aLength);
    IMPORT_C TInt Lock(TInt64 aPos,TInt64 aLength) const;
    IMPORT_C TInt UnLock(TInt64 aPos,TInt64 aLength) const;
    IMPORT_C TInt Seek(TSeek aMode,TInt64& aPos) const;
    IMPORT_C TInt Flush();
    IMPORT_C TInt Size(TInt64& aSize) const;
    IMPORT_C TInt SetSize(TInt64 aSize);
    IMPORT_C TInt Att(TUint& aAttValue) const;
    IMPORT_C TInt SetAtt(TUint aSetAttMask,TUint aClearAttMask);
    IMPORT_C TInt Modified(TTime& aTime) const;
    IMPORT_C TInt SetModified(const TTime& aTime);
    IMPORT_C TInt Set(const TTime& aTime,TUint aSetAttMask,TUint aClearAttMask);
    IMPORT_C TInt ChangeMode(TFileMode aNewMode);
    IMPORT_C TInt Rename(const TDesC& aNewName);
    ...

Registering a plugin to intercept messages

The base class CFsPlugin, has functions that allow a plugin to register to intercept specific types of file server request. This is explained in more detail under Interception of file server requests.

RegisterIntercept()

The function RegisterIntercept() registers a plugin to intercept a specified TFsMessage. The second parameter of RegisterIntercept() allows plugin authors to specify whether the intercept is processed before, after or both before and after the request is processed by the drive thread. See TInterceptAtts.

UnregisterIntercept()

Un-register an intercept with UnregisterIntercept(). UnregisterIntercept() takes TFsMessage, which is the ID of the message and TInterceptAtts, which is the type of intercept that you no longer wish to intercept.

Loading and mounting a plugin

A plugin must be loaded and mounted before it can intercept requests.

Loading and unloading a plugin

Before a plugin can be used by the file server the library (.PXT) that contains the plugin needs to be loaded into the file server process by the loader. Do this using the RFs::AddPlugin() function defined in f32file.h.

AddPlugin() can be called by any user side application with the relevant platform security capabilities.

Unload a plugin using the RFs::RemovePlugin() function also defined in f32file.h.

Note: Refer to the device manufacturer if you want to load plugins automatically at system startup time.

Mounting and unmounting a plugin

File Server plugins are mounted using the RFs::MountPlugin() function defined in f32file.h. The plug in must have already been loaded using RFs::AddPlugin().

There are various overloads of the MountPlugin() function. Those functions which do not take a drive parameter are mounted using KPluginAutoAttach.

Call RFs::DismountPlugin() to dismount the file server plugin. Dismount the plugin when you no longer need it to intercept file server requests.

Before a plugin can be dismounted all resources owned by the plugin must be closed. A plugin can intercept the request to dismount it and use this to free resources and close down cleanly. If the plugin owns resources on behalf of a client (for example, a client has a file open that is using the decompression plugin) then the plugin can reject the request to dismount by returning one of the system wide error codes.

Communicating with plugins

The file server provides a standard logical channel based mechanism that enables a trusted application to communicate with a file server plugin. For example, a virus scanning plugin can present a user interface for the device user. Note: DiskAdmin capabilities are required in order to load a plugin. See platform security capabilities.

Data transfer

The class CFsPluginConnRequest is an information container for asynchronous requests that the client application wants performed. CFsPluginConnRequest also provides functions that enable data to be transferred between the plugin and the application. The following examples show how to use CFsPluginConnRequest.

User-side application

A user-side application uses a class derived from RPlugin to access the plugin. See plugin side. The RPlugin class (defined in f32file.h) provides various functions that must be overridden. The functions DoControl(), DoRequest() and DoCancel() are protected. Plugin authors must provide a public interface of their own design in the derived class that communicates with the plugin using these protected functions.

An RPlugin session is opened by passing in the unique position identifier of the plugin it wishes to communicate with. When the file server receives the request it validates the capabilities of the application before calling the plugin's NewPluginConnL() function. The plugin must certify that it is expecting the client application before creating the server side connection object.

Below is a basic example of an RPlugin derived class:

class RMyPlugin : public RPlugin
{
public:
    inline TInt Enable();
    inline TInt Disable();
    };

inline TInt RMyPlugin::Enable()
    { 
    return DoControl(EEnable); 
    }

inline TInt RMyPlugin::Disable()
    { 
    return DoControl(EDisable); 
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus) const
    {
    RPlugin::DoRequest(aReqNo,aStatus);
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus,TDes8& a1) const
    {
    RPlugin::DoRequest(aReqNo,aStatus,a1);
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus,TDes8& a1,TDes8& a2) const
    {
    RPlugin::DoRequest(aReqNo,aStatus,a1,a2);
    }

TInt MyRPlugin::DoControl(TInt aFunction) const
    {
    return RPlugin::DoControl(aFunction);
    }

This is used in the following way:

Tint r = KErrNone
r = TheFs.AddPlugin(_L("MyPlugin"));
r = TheFs.MountPlugin(_L("MyPlugin"),KPluginSupportAllDrives);
User::LeaveIfError(r);

RMyPlugin thePlugin;
r = thePlugin.Open(TheFs, KMyPluginUniquePos);
User::LeaveIfError(r);
r = thePlugin.Enable();
User::LeaveIfError(r);
...
//Perform communication with plugin here
...
r = thePlugin.Disable();
User::LeaveIfError(r);
thePlugin.Close();
class RMyPlugin : public RPlugin
    {
public:
    inline TInt Enable();
    inline TInt Disable();
    };

inline TInt RMyPlugin::Enable()
    { 
    return DoControl(EEnable); 
    }

inline TInt RMyPlugin::Disable()
    { 
    return DoControl(EDisable); 
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus) const
    {
    RPlugin::DoRequest(aReqNo,aStatus);
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus,TDes8& a1) const
    {
    RPlugin::DoRequest(aReqNo,aStatus,a1);
    }

void MyRPlugin::DoRequest(TInt aReqNo,TRequestStatus& aStatus,TDes8& a1,TDes8& a2) const
    {
    RPlugin::DoRequest(aReqNo,aStatus,a1,a2);
    }

TInt MyRPlugin::DoControl(TInt aFunction) const
    {
    return RPlugin::DoControl(aFunction);
    }

etc.. 
}

This is used in the following way:

Tint r = KErrNone
r = TheFs.AddPlugin(_L("MyPlugin"));
r = TheFs.MountPlugin(_L("MyPlugin"),KPluginSupportAllDrives);
User::LeaveIfError(r);

RMyPlugin thePlugin;
r = thePlugin.Open(TheFs, KMyPluginUniquePos);
User::LeaveIfError(r);
r = thePlugin.Enable();
User::LeaveIfError(r);
...
//Perform communication with plugin here
...
r = thePlugin.Disable();
User::LeaveIfError(r);
thePlugin.Close();

Plugin side

This is the plugin side of the communication. This must be implemented in order to communicate with the application side class RPlugin above. The plugin side is a class derived from the CFsPluginConn class. The plugin’s NewPluginConnL() function is called from the file server when RPlugin::Open() is called by the client.

Synchronous (DoControl()) and asynchronous (DoRequest()) requests sent from the RPlugin class are routed directly to the associated plugin's CFsPluginConn derived class. The following code shows a basic implementation:

class CMyPluginConn : public CFsPluginConn
    {
    virtual TInt DoControl(CFsPluginConnRequest& aRequest);
    virtual void DoRequest(CFsPluginConnRequest& aRequest);
    virtual void DoCancel(TInt aReqMask);
    };

// From the CFsPlugin derived class
CFsPluginConn* CMyPlugin::NewPluginConnL()
    {
    CMyPluginConn* thePluginConn = new(ELeave) CMyPluginConn();
    return thePluginConn;
    }

TInt CMyPluginConn::DoControl(CFsPluginConnRequest& aRequest)
    {
    TInt r = KErrNotSupported;
    CMyPlugin& myPlugin = *(CMyPlugin*)Plugin();
    switch(aRequest.Function())
       {
       case RMyPlugin::EEnable:
          r = myPlugin.Enable();
          break;
       case RLoggerConn::EDisable:
          r = myPlugin.Disable();
          break;
       default:
          break;      
       }
    return r;
    }

void CMyPluginConn::DoRequest(CFsPluginConnRequest& aRequest)
    {
    return KErrNotSupported;
    }

void DoCancel(TInt aReqMask)
    {
    // Not required as no asynchronous request support
    }