Symbian
Symbian Developer Library

SYMBIAN OS V9.4

Feedback

[Index] [Previous] [Next]


How to develop and use File Server Extensions

This document describes how to develop and use a file server extension. It contains the following sections:

Developing an extension describes how to use the APIs defined by the file server to implement a new extension.

Deploying and using an extension describes how an extension can be integrated with the OS, and how clients can access it.

[Top]


Developing an extension

This section describes how to use the APIs defined by the file server to implement a new extension. It contains the following sections:

Project file and build describes the requirements for an extension's project file.

Implementing the factory class describes the factory class that each extension DLL must implement to create proxy driver objects.

Implementing the proxy drive extension class describes the proxy drive class that each extension DLL must implement to provide its extension functionality.

The examples quoted are taken from the t_logext test extension, which is available in the Symbian OS source code at cedar/generic/base/f32test/ext/t_logext.cpp. That test code simply logs when the extension functions are called.


Project file and build

A file server extension is implemented as a polymorphic DLL. Its project file must be written as follows:

The following shows an example project file:

TARGET          t_logext.fxt
TARGETTYPE      fsy

SOURCEPATH  ../ext
SOURCE          t_logext.cpp

SYSTEMINCLUDE       /epoc32/include 

LIBRARY         euser.lib efile.lib

UID     0x100039df 0x10000CEE
VENDORID 0x70000001

#include "../../f32/group/f32caps.mmh"  // Include capabilities of File Server process

Implementing the factory class

Each extension DLL must implement a factory class that is responsible for creating proxy driver objects. This is done by implementing a class derived from CProxyDriveFactory. A pointer to an object of this class must be returned from the first function exported from the extension DLL.

class CLoggerProxyDriveFactory : public CProxyDriveFactory
    {
public:
    CLoggerProxyDriveFactory();
    virtual TInt Install();           
    virtual CProxyDrive* NewProxyDriveL(CProxyDrive* aProxy,CMountCB* aMount);
    };

...

// Create a new factory
EXPORT_C CProxyDriveFactory* CreateFileSystem()
    {
    return(new CLoggerProxyDriveFactory());
    }

Factory initialisation

CProxyDriveFactory defines a pure virtual function Install() that is called once by the file server before any other factory object functions. You must implement this to do any set up required. CProxyDriveFactory is derived from CFsObject, which means that the file server maintains a reference count of its use, and allows clients to refer to the object through a name (a string). The Install() implementation should set the extension's name:

TInt CLoggerProxyDriveFactory::Install()
    {
    _LIT(KLoggerName,"Logger");
    return(SetName(&KLoggerName));
    }

Clients can get the name of an extension using RFs::ExtensionName(), and then use it to specify the extension in such functions RFs::MountExtension().

CProxyDriveFactory also defines a virtual function Remove() that is called just before when the factory object is destroyed. You can override this to do any cleanup required.

Proxy drive factory

CProxyDriveFactory defines a pure virtual function NewProxyDriveL() that is called by the file server to obtain a new proxy drive extension object. You must implement this to create the proxy drive extension.

CProxyDrive* CLoggerProxyDriveFactory::NewProxyDriveL(CProxyDrive* aProxy,CMountCB* aMount)
    {
    return(CLoggerExtProxyDrive::NewL(aProxy,aMount));
    }

The aProxy argument represents the last extension to be mounted. The new extension will be mounted on top of this. aProxy could be an object from another extension library, or a file server CLocalProxyDrive object that calls the media sub-system.

aMount represents the mounted drive to which the extension is being added.

aProxy and aMount are required by the proxy drive extension base class (CBaseExtProxyDrive) constructor:

CLoggerExtProxyDrive::CLoggerExtProxyDrive(CProxyDrive* aProxyDrive, CMountCB* aMount)
    :CBaseExtProxyDrive(aProxyDrive,aMount)
    {
    ...
    }

The arguments are used to initialise private data in CBaseExtProxyDrive.

DLL's first exported function

The DLL's first exported function should create a new instance of the factory class:

EXPORT_C CProxyDriveFactory* CreateFileSystem()
//
// Create a new file system
//
    {
    return(new CLoggerProxyDriveFactory());
    }

Implementing the proxy drive extension class

An extension DLL must implement a proxy drive class that implements the extension. This is done by implementing a class derived from CBaseExtProxyDrive. CBaseExtProxyDrive defines virtual functions that are called by the file system to handle requests for such functions as reading and writing data from the drive. The functions have default implementations that simply pass on the request to the media driver, or possibly, to another extension if one is installed. Your derived class implementation can override these functions to modify this default functionality. Such a function implementation typically performs its particular functionality, then calls the corresponding base class function to pass on the request to the media driver.

The sequence of calls made to extension, and the format of the data passed in those calls, will vary according to the file system type. To implement a file system extension therefore, you need a good understanding of the particular file system for which the extension is targeted.

Initialisation

The file server calls the proxy drive class Initialise() function after construction. You can override this function to do any set up you require. Your implementation should end by calling the base class function CBaseExtProxyDrive::Initialise() to pass on the request.

TInt CLoggerExtProxyDrive::Initialise()
    {
    // extension specific initialisation 
    // ...
    return(CBaseExtProxyDrive::Initialise());
    }

The file system may at initialisation and at later times request capability information from the drive using the Caps() function:

TInt Caps(TDes8 &anInfo);

On return, it is expected that anInfo is a packaged capability structure, as defined in d32locd.h. CBaseExtProxyDrive implements Caps() to get the capabilities from the drive.

Cleanup

If the drive is dismounted, the extension is notified by a call to its Dismounted() function, and the proxy drive object is deleted.

Reading data from the drive

A file system requests to read data from the drive by calling the proxy drive's virtual Read() functions, of which there are three overloads. A proxy drive extension can override these functions to modify the read functionality. The implementation can as usual pass on the request to the drive using the base class function implementations.

TInt CLoggerExtProxyDrive::Read(TInt64 aPos,TInt aLength,TDes8& aTrg)
    {
    // do extension specific operations
    // ...
    return(CBaseExtProxyDrive::Read(aPos,aLength,aTrg));
    }

Writing data to the drive

A file system requests to write data to the drive by calling the proxy drive's virtual Write() functions, of which there are three overloads. A proxy drive extension can override these functions to modify the write functionality. The implementation can as usual pass on the request to the drive using the base class function implementations.

TInt CLoggerExtProxyDrive::Write(TInt64 aPos,const TDesC8& aSrc)
    {
    // do extension specific operations
    // ...
    return(CBaseExtProxyDrive::Write(aPos,aSrc));
    }

It is possible for some media, such as a RAM disk type drive, to be dynamically resized to fit the data being written to it. The proxy drive interface Enlarge() and ReduceSize() functions handle such resizing requests. An extension can override these functions if required.

Formatting a drive

A file system requests to format a drive by calling the proxy drive's virtual Format() functions, of which there are two overloads. A proxy drive extension can override these functions to modify the drive format functionality. The implementations can as usual pass on the request to the drive using the base class function implementations.

Password and drive locking

Some drive types can be locked using a password, using functions such as RFs::LockDrive(). The proxy drive class defines a number of functions, Clear(), ErasePassword(), Lock(), and Unlock(), to handle such requests. Extensions can override these functions and pass on the request to the drive using the base class implementations.

Error information

File systems can in some circumstances request information from the media driver about the last error that occurred. The proxy drive class defines the function GetLastErrorInfo() to handle such requests. Extensions can override this function or use the default implementation provided by the base class. The error information is a packaged TErrorInfo structure.

Extension functions

The proxy drive class implements the common Symbian OS pattern that allows new functionality to be added to an existing class without altering the interface, which could cause a compatibility break. The GetInterface() function takes an ID argument indicating the functionality being requested, and generic input and output arguments. An extension can override this if required.

TInt CLoggerExtProxyDrive::GetInterface(TInt aInterfaceId,TAny*& aInterface,TAny* aInput)
    {
    switch(aInterfaceId)
        {
        // file caching supported, so pass query on to next extension
        case ELocalBufferSupport:
            return CBaseExtProxyDrive::LocalBufferSupport();

        default:
            return CBaseExtProxyDrive::GetInterface(aInterfaceId, aInterface, aInput);
        }
    }

Test operations

The file server offers an interface, RFs::ControlIo(), that allows in debug builds a message to be sent to a media driver, in order to simplify writing test programs.

The proxy drive class defines the function ControlIO() to handle such requests. Extensions can override this function or use the default implementation provided by the base class.

[Top]


Deploying and using an extension

An extension library is only called after a client has requested the file server to load the extension library and to mount it on a particular drive. This section first describes how to do this at the same time as mounting the target file system, and then how to add an extension to an already mounted file system.


Mounting extensions in EStart

To mount a primary extension, modify EStart to use the RFs:AddExtension() and RFs:MountFileSystem() APIs as shown below.

// If an extension is required, assume its name is set in iExtName
if (iExtName)
    {
    TPtrC extname(iExtName);
    r=iFs.AddExtension(extname);
    if (r==KErrNone || r==KErrAlreadyExists)
    r=iFs.MountFileSystem(fileSysName,extname,drive);
    }
else
                r=iFs.MountFileSystem(fileSysName,drive);

RFs::MountFileSystem() can be passed a flag to specify whether the drive is mounted as synchronous or asynchronous. An asynchronous drive has its own processing thread, i.e. operations on it do not block the file server and other drives. A synchronous drive's requests are processed by the main file server thread and it is possible to block it with operations on other drives. A drive should be mounted as synchronous if it is very fast, such an internal RAM or ROFS drive. RFs::DismountFileSystem() is used to dismount a file system and any primary and secondary extensions mounted on the drive.

No resources can be open on the drive while an extension is mounted as this operation involves a dismount/remount. If the remount fails, then the extension is dismounted from the drive.


Mounting by client-side request

The functions in the file server client API allow extensions to be managed by file server clients.

Most of these functions require the name of the extension. Use RFs::ExtensionName() to get the name of an extension on the specified drive at position aPos. The position is the location in the extension chain, the first extension added is equal to zero. If an extension is not found at aPos then KErrNotFound is returned.

    const TInt KMaxFileSystemExtNameLength = 100; // Arbitrary length
    TBuf<KMaxFileSystemExtNameLength> fsExtName;
    r = TheFs.ExtensionName(fsExtName, driveNo, 0);

To mount a secondary extension it must first be added to the file server using RFs::AddExtension(). This loads the specified extension and adds it to the extension container in the file server. The extension can then be mounted onto the specified drive using RFs::MountExtension().

To dismount an extension from a specified drive, use RFs::DismountExtension(). This can only dismount extensions that were mounted using RFs::MountExtension(). No resources can be open on the drive when an extension is dismounted. This operation involves a dismount/remount if there is a current CMountCB object mounted on the drive. An extension can be removed from the file server using RFs::RemoveExtension().

[Top]


Possible issues with file server extensions

Loading other libraries, such as device drivers and DLLs, from an extension, uses the Kernel's Loader which uses the file server. This raises the possibility of deadlock. Even if your file system extension is for a different file system than from the one where your driver lives, the Loader may still scan other drives.

Note that CProxyDriveFactory::Install() is called from the file server's main thread, so using the file server from there is guaranteed to deadlock. The simplest solution to this would be to have the client that calls RFs::AddExtension() load the required library beforehand.

An alternative approach would be to create a new thread from the Install() function and, in order to load a driver, call User::LoadLogicalDevice() in that thread. Your code would need to solve the synchronisation problem of ensuring that the User::LoadLogicalDevice() call has completed before attempting to mount the extension on a drive.