|
||
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.
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.
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
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());
}
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.
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
.
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());
}
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.
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.
If the drive is dismounted, the extension is notified by a call to
its Dismounted()
function, and the proxy drive object is deleted.
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));
}
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.
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.
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.
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.
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);
}
}
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.
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.
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.
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()
.
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.