|
||
The mechanisms of key spaces, capabilities and SIDs are used to write the content of an initialisation file.
The owner of a repository (the application responsible for backing it up) is identified by its SID. Example:
[owner]
0x12345
Settings of a repository are given values and also metadata values. The Default Metadata section of a repository gives the settings global default metadata values which may be overridden by individual metadata values assigned in the Main section. A metadata value is held as a 32 bit integer whose binary digits encode separate items of metadata. The most significant eight bits (most significant byte) of the integer are reserved for internal purposes. The other 24 bits of the integer have no reserved significance. Two of the eight bits are exposed for use by application developers.
The least significant bit of the most significant byte is set to 1 to indicate that a backup operation applies to the setting.
The second least significant bit of the most significant byte is set to 1 to indicate that a restore factory settings operation applies to the setting.
What is meant by least and most significant? An example may help. A hexadecimal number such as 03020100 is stored as a single 32 bit integer. The integer can be analysed as a sequence of four bytes:
03 02 01 00
The leftmost byte, 03, is the most significant byte because it represents a larger quantity (03000000) than the other three (02000, 0100 and 00). Each byte consists of eight bits. In binary notation they are
00000011 00000010 00000001 00000000
The rightmost bit of the leftmost byte is the least significant bit of the most significant byte because it represents a smaller quantity (1) than its neighbour to the left (10) which is the second least significant bit of the most significant byte. These are the two exposed bits of the metadata value and the hexadecimal integer 03020100 has both of them set to 1. The 32 bits are sometimes referred to by number (32 for the leftmost down to 1 for the rightmost): thus the exposed bits are also called bits 26 and 25.
Global default metadata values consist simply of a 32 bit integer. Default metadata values can also be applied to a range of settings by prefixing either a pair of keys representing a continuous range, or else a partial key and a key mask prefixed by "mask=". Example:
[defaultMeta]
0x00000010
0x100 0x400 0x00000020
0x1000 mask = 0x04 0x00000040
An access policy grants read or write permissions on a setting or a group of settings to an application or a group of applications. Applications are identified by their SID and groups of applications by their capability name.
Decide which applications you want to grant permissions to, and whether you are going to identify them by capability or SID. SIDs are unsigned integers which identify an application and are assigned by Symbian Signed. Identify the settings and groups of settings which are to be written and read: you will need their keys and partial keys. Determine which applications need read or write permissions on which settings.
You create an access policy by combining these elements using the following syntax.
A capname is one of 'TCB' 'CommDD' etc. A caplist is two or three capnames separated by a comma and whitespace. A read caps statement is "cap_rd" followed by a capname or a caplist.
A read sid statement is "sid_rd" followed by an SID.
A read policy is a read sid statement or a read caps statement or one of each separated by whitespace. A write policy is the same as a read policy with "_wr" instead of "_rd". An access policy is a read policy or a write policy or one of each separated by whitespace.
Instead of granting or denying permissions to specified applications, you can create global permissions by using the words 'AlwaysPass' or 'AlwaysFail' in place of an SID. For instance, 'sid_rd AlwaysPass' grants read permissions to all applications, 'sid_wr AlwaysFail' denies write permissions to all applications and so on.
An access policy constructed on these lines with no further qualification creates permissions on all the settings in a repository (this is called a default policy). To create permissions on individual settings (a single policy) or a group of settings (a range policy or a mask policy depending on implementation) we use the keys of those settings.
A single policy is a default policy prefixed with a key referring to a single setting.
A range policy is a default policy prefixed with two keys, the lowerkey and the upperkey. The policy assigns permissions on all settings with keys between the lowerkey and the upperkey inclusively.
A mask policy is an access policy prefixed with a partial key and then "mask=" and a keymask.
A custom policy is a single policy, a range policy or a mask policy. A policy list is a custom policy or several custom policies separated by whitespace.
We have now defined the structure of the access policy section of an initialisation file. It consists of a line reading "[PlatSec]" followed by a default policy or a policy list or one of each separated by whitespace. Where several policies in a list apply to the same key, the later policy overrides the earlier. The read policies, each on a separate line, must precede the write policies, each on a separate line.
Initial values are assigned to settings in the Main section of an
initialisation file. Each line begins with a setting identified by an
individual key or a group of settings identified by a pair of keys representing
a range or else a keymask and a partial key. Following the key or keys comes
the data type of the setting, one of int
, real
,
string, string8
or binary
. Next comes the actual
initial value of the setting and finally, as an optional item, the metadata
value of the setting. Example:
[main]
1 int 1 0
2 real 2.732 0xa
5 string "test\\\"string\"" 2
6 int 12 0xf
8 real 1.5 1
11 string string 0x305
12 string8 string 0x305
0x11 real 1.5 12
0x101 int 100 0
You use an initialisation file to register a repository by saving it to
device memory. Ideally you do this at ROM build time and save to the directory
z:\private\10202BE9\
(this is the Central Repository directory,
named after its UID). It is also possible to register a repository after build
time using the Symbian OS Software Installer to save it to the C drive. This
process requires a signed SIS file and is explained below. In either case, the
file name of the repository is the same as its UID.
A repository may be saved to memory as a text file. The encoding must be UTF-16, no other format being supported. However, you are recommended to convert this text file to binary format and save the binary to device memory for reasons of performance. Retrieval times are an order of magnitude faster using binary files. You convert text to binary with the tool CentRepConv.exe as documented in CentRepConv User Guide. CentRepConv.exe can also be used to convert binary files back to text format (in a slightly lossy way due to differences in the specification of the two formats).
This section describes how to make an application read and modify a repository at runtime from function calls in its application code.
You open a repository by creating a CRepository
object in
your code: there is no Open() function. To open several repositories you must
create one CRepository
object for each of them. These objects
encapsulate client views of the Central Repository: data integrity is
maintained by server classes which are not part of the exposed API. You read
and modify the data in the repository using CRepository
functions
such as Get()
and Set()
. In order to prevent
different clients corrupting the repository data with concurrent write
operations, these operations are wrapped in sequences called transactions.
Transactions will be explained in the next section. You close a repository by
deleting the CRepository
object: there is no Close() function.
The keys are used in the initialisation file to identify data items as we saw above. They are also used as parameters of the API functions which are used to get, set, create, delete, reset and find settings.
Various functions of a CRepository object read the values of the repository settings. These functions all return an error message (KErrNone on success). The data requested by a function is returned as the value of a parameter of the function, and the data type of the parameter must match the data returned.
The Get() functions of a CRepository object take individual keys as parameters and retrieve the corresponding values. You cannot directly retrieve values by specifying a range of keys: you must convert a range to a list of individual keys and pass those keys to a Get() function.
The Find() functions of a CRepository object convert a range of keys into a list of individual keys. The input parameters are a partial key and a key mask, defining the range: the list of keys is returned as the value of another parameter. The list may contain all the keys in the range, or may be restricted to keys with specified values. The restricted Find() functions take the value of a setting as an input parameter and return variously those keys in the range whose values are equal to the parameter or those which are not equal to it.
The GetMeta() function of a CRepository object reads the metadata value assigned to a key. A metadata value is a 32 bit hexadecimal number of which the most significant 8 bits are reserved. The reserved bits should normally be masked out so that they cannot be used. To do this you use a constant defined for the purpose, KMetaUnreserved, with logical AND. A metadata value AND KMetaUnreserved has the reserved bits set to 1.
The Set() functions of a CRepository object write the values of a setting to a repository where the key already exists.
To create a new key and write its value, use the Create() functions of a CRepository object.
To delete a key and its value, use the Delete() functions. You can delete a group of keys using the partial key and key mask mechanism.
To restore the default value of a setting, use the Reset() functions of a CRepository object. The default value of a setting is the most recent value which was explicitly assigned to it, or else the value assigned in the initialisation file. If no value was ever assigned, a call to Reset() deletes the setting.
An access policy cannot be changed at run time. The permissions created on an area of keyspace are static: that area always has those permissions. However, it is possible to modify access to a setting at runtime by moving it from one area of keyspace to another area with different permissions. You do this by calling the Move() function of the CRepository class.
TInt CRepository::Move (TUint32 aSourcePartialKey, TUint32
aTargetPartialKey, TUint32 aMask, TUint32 &aErrorKey)
The parameters aSourcePartialKey
and aMask
define one range of keys, the source, and the parameters
aTargetPartialKey
and aMask
define another, the
target. The effect of calling the function is to cause the values formerly
accessed using the source keys to be accessed using the target keys. If the
target keys have different permissions from the source, those permissions will
in future govern access to the values concerned.
It will often be the case that your application accesses a repository
in order to read data written by a separate application. If so, your
application must request to be notified whenever the other application modifies
the relevant settings. To request notification of changes to a setting or group
of settings you use the RequestNotify()
functions of a CRepository
object with a TRequestStatus
object as parameter. The
value of this parameter changes when the notification server detects a change
to the repository. If a single setting has changed, the parameter takes the
value of that setting: if more than one setting has changed, the parameter
takes the value KUnspecifiedKey
. To receive notification of
changes, your code must periodically poll this key. It must also renew the
RequestNotify()
call before reading the setting which has changed.
This is important because several changes may occur in quick succession and you
want to read the latest state of the repository.
You cancel notifications about specific settings or groups of settings
using NotifyCancel()
, and you cancel all notifications using
NotifyCancelAll()
.
In a typical use case, a repository is accessed by several different applications for different purposes such as reading, updating, caching and backing up. These applications are regarded as having a client-server relationship with the repository. When multiple applications can access a repository there is a danger of concurrent write operations corrupting the data. To prevent this, applications access repositories within sessions and perform operations on a repository within subdivisions of a session called transactions. The concept of a transaction is borrowed from database programming and is designed to ensure that only one application can modify a repository at any one time. Operations performed within a transaction are virtual, operating on a copy of the repository, until the transaction is successfully committed and the actual repository is modified. Transactions conform to a model which prevents them from being committed concurrently, so maintaining data integity.
Only the following operations are permitted within a transaction:
Find()
Get()
Set()
Create()
Delete()
Move()
Transactions are either synchronous or asynchronous. As the programmer you are responsible for avoiding errors such as beginning a transaction within a transaction which will cause a leave. You do this by conforming to a transaction model.
The recommended transaction model is the optimistic non-serialised transaction model. It works on the principle that any number of clients may start a transaction at the same time, but as soon as one transaction is committed, any other transactions fail and must be started again.
A session is in various states:
Not in transaction
Active
Failed
An asynchronous transaction involves two other states:
Pending start
Pending commit
A synchronous transaction has the following structure.
A session is initially in the state Not in transaction.
Call the synchronous form of StartTransaction()
with the single parameter
EConcurrentReadWriteTransaction.
The session changes state to Active.
Manipulate the repository with the functions
Get()
Set()
Find()
etc covered above.
The session continues in the Active state. The changes to the repository are cached but not actually applied during the Active state. You then either revoke or persist the changes.
To revoke the changes, call CancelTransaction()
The session returns to Not in transaction.
To persist the changes, call the synchronous form of
CommitTransaction()
with the parameter
aKeyInfo
of type TUint32
This function
returns success or an error message and its parameter holds information about
the transaction. On success, the parameter holds the number of settings which
were changed. On failure, the parameter holds the settings which caused failure
in the form of a key or partial key.
On success the session returns to Not in transaction. On failure it enters the Failed state. When a transaction fails, the operations already performed are discarded and no subsequent operations can reverse the failure.
If the reason for failure was the success of another transaction, the
error message is KErrLocked
and the session is Not in
transaction. You try the same sequence of function calls until you get a
successful commit.
If there was some other reason for failure you must close the
transaction yourself. To do this, either call
CancelTransaction()
or else call
CommitTransaction()
a second time.
There is sometimes reason to fail the transaction deliberately by
calling FailTransaction()
For instance you might want to
generate failure after an unsuccessful Get()
operation and
then cancel the transaction.
Synchronous transactions have the disadvantage that a busy server may block the client thread before any action takes place. To avoid this problem you can use the slightly more complicated asynchronous transactions. An asynchronous transaction has the same structure as a synchronous one except that there are two new states Pending Start and Pending Commit where a transaction waits until the server is ready to communicate with it. The transition from Not in transaction to Active now proceeds like this.
A session is initially in the state Not in transaction.
Call the asynchronous form of StartTransaction()
with the parameters EConcurrentReadWriteTransaction
and
aStatus
of type TRequestStatus
This
function returns void: success or the reasons for failure are determined from
the state of aStatus
after execution.
The session changes state to Pending Start. Activity on the server will eventually promote the session to Active.
Manipulate the repository with the functions
Get()
, Set()
,
Find()
etc covered above.
The session continues in the Active state. The changes to the repository are cached but not actually applied during the Active state. You then either revoke or persist the changes.
To revoke the changes, call
CancelTransaction()
This call has different effects
depending on whether it was called in the Pending Start state or the Active
state. If it was called in the Pending Start state it sets
aStatus
to KErrCancel
The session returns to Not in transaction.
To persist the changes, call the asynchronous form of
CommitTransaction()
with the parameters
aKeyInfo
of type TUint32
and
aStatus
of type TRequestStatus
This
function returns void. Its first parameter holds information about the
transaction.
On success, the aStatus
parameter holds the
number of settings which were changed. On failure, the parameter holds the
settings which caused failure in the form of a key or partial key. The
aStatus
parameter is set to success or failure.
If the reason for failure was the success of another transaction,
aStatus is set to KErrLocked
and the session is Not in
transaction. You try the same sequence of function calls until you get a
successful commit.
If there was some other reason for failure you must close the
transaction yourself. To do this, either call
CancelTransaction()
or else call
CommitTransaction()
a second time.
There is sometimes reason to fail the transaction deliberately by
calling FailTransaction()
For instance you might want to
generate failure after an unsuccessful Get()
operation and
then cancel the transaction.
There is a danger that a transaction might remain open for ever if it is
opened by code which subsequently leaves. To avoid this possibility, the
CCentralRepository
class has two functions which cause the
cleanup stack to end the transaction in the event of a leave.
CleanupCancelTransactionPushL()
, also named
CleanupRollbackTransactionPushL()
, causes a leave to be
followed by a call to CancelTransaction()
.
CleanupFailTransactionPushL()
causes a leave to be
followed by a call to FailTransaction()
.
The performance of applications which use the Central Repository can be improved by fine tuning the use of cache memory. The Central Repository supports caching by default but you can achieve further improvement by modifying the settings of the cache configuration file. This file is called cenrep.ini and is located either on the system drive or on the Z drive. Its contents look like this.
[CoarseGrainedCache]
size= 100000
timeout= 500000
The two parameters which can be configured are:
size
(the number of bytes reserved for caching)
timeout
(the time in microseconds to hold repositories
in the cache)
The timeout parameter controls a countdown during which a repository is held in the cache. The countdown begins when a repository ceases to be in any session of a CRepository object, unless you specify a notify-only client optimisation as discussed below. It ends when the value timeout is reached: the repository is then evicted from the cache.
The purpose of configuring centrep.ini is to improve performance of the Central Repository as a whole, rather than of applications individually. The optimum values of the parameters must be established by trial and error.
The behaviour of the timeout can be modified by specifying a notify-only client optimisation. This means that the countdown during which a repository is cached begins after the last access operation of any type on that repository, even if the session is still in progress.
A notify-only client optimisation is specified by including the system-wide macro
#define SYMBIAN_CENTREP_NOC
You can disable caching altogether by setting a cache size of 0 in centrep.ini
[CoarseGrainedCache]
size= 0
timeout= 0
To provide for backup of a repository you must identify an owner application responsible for backup. This must have a suitable backup registration file (as used for all backup). By default, repository data is not backed up: you must also write the repository initialisation file to enable backup. In the initialisation file you specify the owner application by SID in the owner section and use the metadata values to specify the settings to be backed up. A setting is backed up if bit 25 of its metadata value is set. You can set this bit for the whole repository in the defaultMeta section or individually for settings and groups of settings using the metadata fields of the main section.
There are two ways of equipping an application with a keyspace when the
application is installed on the device. One is to save the keyspace to the
directory z:\private\10202BE9\
at ROM build, as explained above.
This method has the disadvantage that a keyspace created in this way cannot
subsequently be upgraded or uninstalled. The other way is to install the
keyspace along with the application using the software installer. Only device
creators are able to do this.
You install a keyspace by placing a stub .sis file (installation file)
in the \System\Install
directory in ROM. This file is written by
device creators rather than developers. It must contain the central repository
server executable. It must list all the ROM-supplied keyspace files which are
intended to be upgradable.
The following template is supplied for use in device creation. Device creators are free to modify the major and minor version numbers but are strongly recommended to retain the UID 0x20007770 which is allocated for use with the central repository.
;
; template_stub.pkg
;
; Template Central Repository Stub Package File
;
; Copyright (c) 2006 Symbian Software Ltd. All rights reserved.
;
; Language
&EN
; Package Header
;
; #{"Package name"}, (Package UID), major-version, minor-version, build-number
;
; You are free to modify the version and build numbers but the package UID
; should NOT be altered.
;
#{"Centrep Stub"}, (0x20007770), 0, 0, 0
; Vendor
%{"Symbian Software Ltd."}
:"Symbian Software Ltd."
; Central Repository Server - this must be in the stub package file
"<INSERT-LOCAL-PATH-HERE>centralrepositorysrv.exe"-"z:\sys\bin\centralrepositorysrv.exe"
; eg "\epoc32\release\armv5\urel\centralrepositorysrv.exe"-"z:\sys\bin\centralrepositorysrv.exe"
; List of upgradeable ROM keyspaces
;
; If you wish to provide the ability for a keyspace supplied in ROM to be upgraded
; by Software Install then list it below. Any keyspaces not included in this list
; may not be upgraded.
;
; Each entry in the list should take the following form:
; ""-"z:\private\10202be9\<keyspace-file.ext>"
;
; For example:
; ""-"z:\private\10202be9\ace1ace1.cre"
REPLACE THIS LINE WITH THE KEYSPACE LIST OR REMOVE IF ROM KEYSPACES SHOULD NOT BE UPGRADED
Installing an application is explained here and upgrading is explained here. Upgrading software in ROM is explained here. It involves using the CreateSIS tool to compile files listed in a package (.pkg) file into an installation (.sis) file. A package file may refer to an existing .sis file: if so, the .sis file is said to be embedded. An upgrade is essentially the installation of a software patch. You upgrade a keyspace in the same way as you upgrade the associated application and the two operations are often combined. Installing a keyspace is the same as upgrading one: you are eclipsing the stub .sis file discussed in the previous section. To do this you need:
the upgrade .pkg file and corresponding .sis file,
and the application .pkg file and corresponding .sis file if you are upgrading the application.
A third party developer cannot create a stub .sis file: it must have been created as part of the device's ROM. If one does not exist, you cannot upgrade a keyspace.
The upgrade .sis file must be signed.
You do not need the application .sis and .pkg files if you are installing or upgrading a keyspace without the associated application.
In both cases you write a .pkg file with details of the software upgrade. It can upgrade the application and the keyspace at the same time or could upgrade the keyspace alone as shown in the following example:
&EN
#{"Example new keyspace"}, (0x20007770), 1, 2, 3, TYPE=SP
%{"Symbian Software Ltd."}
:"Symbian Software Ltd."
; Install keyspace in Central Repository’s private data cage
"12345678.cre"-"c:\private\10202be9\12345678.cre"
The important thing is that the package UID, (0x20007770), must match the UID in the stub package header. You compile the .pkg file into a .sis file and, if required, go through the signing process at this point. At this point you can deploy the keyspace .sis file on its own. However, it is more usual to embed the keyspace .sis file into your application .pkg file and install the application.
You uninstall a keyspace by uninstalling the application which it was embedded in. This has the effect of uninstalling all the software patches with which you upgraded the keyspace. If there is a version of the keyspace in ROM, placed there at device creation, it comes back into use. If the keyspace was installed subsequently then no version of it remains after uninstallation. It is not possible to remove individual upgrades, only the entire set of upgrades which have ever been applied.
An upgrade to a keyspace may conflict with user modifications to the keyspace. In that case the existing settings are merged with those in the upgrading keyspace in accordance with the following rules. The keyspaces are merged in accordance with the principle that user modifications override upgrade modifications.
If a setting exists in the existing keyspace on the phone and the upgrading keyspace and the existing setting has not been modified by the user, the upgrading setting replaces the existing setting.
If a setting exists in the existing keyspace and the upgrading keyspace and the existing setting has been modified by the user, the existing setting is preserved.
If a setting exists in the existing keyspace but not in the upgrading keyspace, the existing setting is preserved.
If a setting exists in the upgrading keyspace but not in the existing keyspace, the upgrading setting is created.
Upgrades preserve the existing platform security policies. Metadata values assigned by default or to a range remain unchanged, but metadata values of individual keys take their upgrade settings.
The first time you upgrade a keyspace which is provided in ROM you only need to list the keys which are being added to or changed from the ROM keyspace. However, if you are performing an upgrade to a keyspace which has previously been upgraded, you must include all the changes in all the previous upgrades: this is also the case where the keyspace was originally installed as an upgrade to a stub .sis file.
If there are notification requests on any of the keys, the requests will complete as a result of installation, upgrading and uninstallation. If transactions are pending during an upgrade or uninstallation, those transactions will fail. It is advisable to close all sessions on a keyspace before uninstalling.
Two classes of error can occur when a keyspace is installed, upgraded or uninstalled. One is installation of a corrupt repository (one which does not conform to the specified text or binary format). If the central repository detects corrupt repositories, it deletes them. It is preferable to install a repository in binary format as corrupt files will then be detected at build time.
The other class of possible error is system failures such as insufficient memory. These prevent the keyspaces from merging and the result is loss of synchronisation between applications and their repositories. The remedy for errors of this kind is to maintain versioning information within the repositories, so that the version of a repository can be checked when it is opened and validated against the version expected by the client.