File Server Extensions Tutorial

Describes how to develop and use a file server extension.

See File Server Extensions for more information about the file server extension architecture.

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:

The examples quoted are taken from the t_logext test extension, which is available in the Symbian platform source code at os/kernelhwsrv/kerneltest/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:

  • TARGETTYPE should be set to fsy. This means that the project does not require a .def file to specify its frozen exports: the build tools will assume the correct frozen exports for the type.

  • The DLL name is conventionally given the extension .fxt.

  • The second UID should be 0x100039df.

  • The DLL is loaded by the file server, and therefore must have the same platform security capabilities as that process: TCB ProtServ DiskAdmin AllFiles PowerMgmt CommDD.

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 by 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 function. You must implement this to do any required setup. 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() function's 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 functions such as RFs::MountExtension().

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

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 it. 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 contain 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 such 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 the extension, and the format of the data passed in those calls, vary according to the file system type. Therefore, to implement a file system extension 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, 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 should as usual pass on the request to the drive using the base class's functions.

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 should as usual pass on the request to the drive using the base class function.

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's 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 should as usual pass on the request to the drive using the base class functions.

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 should pass on the request to the drive using the base class functions.

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 platform 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 a message to be sent to a media driver in debug builds, 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.

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 the Base Starter

To mount a primary extension, modify the Base Starter to use the RFs:AddExtension() and RFs:MountFileSystem() functions 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().

Possible issues with file server extensions

Loading other libraries (such as device drivers and DLLs) from an extension uses the kernel's loader, which in turn 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 is 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.