#
# Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of the License "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description: 
#

package AppGen;
use strict;

my $BuildSystemVersion = $ENV{'SBS_VERSION'};

##############################################
## the object constructor                   ##
##############################################
sub new
{
    my $self = {};
    
    shift @_;
    
    my $actual_num = scalar(@_);
    $self->error("Expected 9 args, got $actual_num") if ($actual_num != 9);
    
    $self->{PLATFORM} = shift @_;
    $self->{VARIANT} = shift @_;
    $self->{TARGET_NAME} = shift @_;
    $self->{OUTPUT_DIR} = shift @_;

    $self->{UTIL} = shift @_;
    die "util not defined\n" if not $self->{UTIL};

    $self->{INPUT_FILE} = shift @_;
    $self->{UID} = shift @_;
    $self->{VID} = shift @_;
    $self->{INSTALL_DRIVE} = shift @_;
    
    $self->{GENERATE_PKGFILE} = 0;
    $self->{GENERATE_SISFILE} = 0;
    
    $self->{PKGFILE} = ($self->{OUTPUT_DIR} . "/" . $self->{TARGET_NAME} 
			. ".pkg");

    my $input_file = $self->{INPUT_FILE};
    
    if ($input_file)
    {
	-e $input_file or 
	    $self->{UTIL}->error("Input file '$input_file' does not exist\n");
    }

    $self->{UTIL}->detailed("variant: ", $self->{VARIANT}, "\n");
    $self->{UTIL}->detailed("target_name: ", $self->{TARGET_NAME}, "\n");
    $self->{UTIL}->detailed("output_dir: ", $self->{OUTPUT_DIR}, "\n");
    $self->{UTIL}->detailed("platform: ", $self->{PLATFORM}, "\n");
    $self->{UTIL}->detailed("variant: ", $self->{VARIANT}, "\n");
    
    $self->{OUTPUT_DIR} =~ m|(.*)[/\\]+.*$|;
    $self->{ROOT} = "$1\\..";

    bless($self);
    return $self;
}

##############################################
## Class methods to access per-object data  ##
##                                          ##
##############################################

#
# Perform environment checking
#
sub generate_prerequisites()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));
 
    my $util = $self->{UTIL};
    $util->detailed("Checking environment for source generation\n");

    my $output_dir = $self->{OUTPUT_DIR};

    $util->error("$output_dir already exists") if -e $output_dir;
    mkpath($output_dir, 1, 0777);
    $util->error("Could not create $output_dir") if not -d $output_dir;
    
    my $input_file = $self->{INPUT_FILE};
    $util->error("'$input_file' does not exist") if not -e $input_file;
    
    $util->error("A UID is mandatory for generating sources") 
	if (not $self->{UID});
    
    my $uid = $self->{UID};
    my $vid = '';
    if (defined($self->{VID}))
    {
	my $vid = $self->{VID};
    }
    
    $util->detailed("Using UID and VID {$uid,$vid} respectively\n");
}

#
# Perform environment checking
#
sub pkg_prerequisites()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $util = $self->{UTIL};

    my $localised_vendor = shift;
    my $non_localised_vendor = shift;
    my $suid = shift;

    if ($localised_vendor or $non_localised_vendor or $suid)
    {
	$util->detailed("Checking environment for pkg file generation\n");

	if (not ($localised_vendor and $non_localised_vendor and $suid))
	{

	    if (not $localised_vendor)
	    {
		$util->error("localised_vendor must be specified");
	    }

	    if (not $non_localised_vendor)
	    {
		$util->error("non_localised_vendor must be specified");
	    }
	    
	    if (not $suid)
	    {
		$util->error("SIS UID must be specified");
	    }

	    # This is a weak check since source generation and
	    # compilation (including SIS file creation) may occur at
	    # different times.  Thus the uid may be NULL
	    
	    my $uid = $self->{UID};
	    
	    if ($uid eq $suid)
	    {
		$util->error("SIS file UID and Application UID ", 
			     "should not be the same");
	    }
	} 

	$self->{GENERATE_PKGFILE} = 1;
    } 
}

#
# Generate sources
#
sub generate(@)
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));
    
    my $output_dir = $self->{OUTPUT_DIR};
    my $util = $self->{UTIL};
    
    my $major = shift;
    my $minor = shift;
    my $buildnum = shift;
    my $suid = shift;
    my $localised_vendor = shift;
    my $non_localised_vendor = shift;

    $self->generate_prerequisites();
    $self->pkg_prerequisites($localised_vendor, $non_localised_vendor, $suid);

    $util->detailed("Output will be generated in $output_dir\n");
    
    
    
    $self->make_source_src();
    $self->make_make_src();
    $self->make_doc_src($major, $minor, $buildnum);
    
    if ($self->{GENERATE_PKGFILE})
    {
	$self->make_pkg_src($major, $minor, $buildnum, $suid, 
			    $localised_vendor,
			    $non_localised_vendor);
    }
}

#
# Perform environment checking
#
sub make_prerequisites(@)
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $util = $self->{UTIL};

    $util->detailed("Checking environment for building\n");
    
    my $output_dir = $self->{OUTPUT_DIR};
    $util->error("$output_dir does not exist") if (not -d $output_dir);
    
    my $platform = $self->{PLATFORM};
    $util->warning("Platform '$platform' untested.")
	if ($platform =~ /"winscw"/i and $platform =~ /"armv5"/i);
    
    my $variant = $self->{VARIANT};
    $util->error("$variant not supported") 
	if ($variant =~ /"urel"/i and $variant =~ /"udeb"/i);
}

#
# Perform environment checking
#
sub sis_prerequisites()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $key = shift;
    my $certificate = shift;
    
    my $util = $self->{UTIL};

    if ($key or $certificate)
    {
    	$util->detailed("Checking environment for SIS file creation\n");

	# Passphrase is optional
	$util->error("Both Key and certificate must be specified") 
	    if (not ($key and $certificate));
	
	-e $key or $util->error("$key does not exist");    
	-e $certificate or $util->error("$certificate does not exist");

	$self->{GENERATE_SISFILE} = 1;
    }
}

#
# Build the generated source file.
#
sub make(@)
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));
    
    my $target_name = $self->{TARGET_NAME};
    my $output_dir = $self->{OUTPUT_DIR};
    my $platform = $self->{PLATFORM};
    my $variant = $self->{VARIANT};
    my $util = $self->{UTIL};

    my $key = shift;
    my $certificate = shift;
    my $passphrase = shift;
    
    $self->make_prerequisites();
    $self->sis_prerequisites($key, $certificate);

    $util->info("Building...\n");
    
    $util->info("Target Executable: ");
    $util->info("\\epoc32\\RELEASE\\$platform\\$variant\\${target_name}.exe\n");
    use Cwd;
    my $whence = Cwd::cwd();
    
    chdir "$output_dir" or$util->error("chdir $output_dir failed");
    
 	if ($BuildSystemVersion == "2") 
 	{
 		my $platformConfig = "$platform"."_"."$variant";
 		$util->invoke("sbs -c $platformConfig");
 	}
 	else
 	{
 		$util->invoke("bldmake bldfiles");
 		$util->invoke("abld build $platform $variant");
 		$util->invoke("abld -check build $platform $variant");
 	}
 	
   
    chdir "$whence" or $util->error("chdir $whence failed");

    if ($self->{GENERATE_SISFILE})
    {
	$self->make_sis($key, $certificate, $passphrase);
    }
}

sub make_doc($)
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    use Cwd;
    my $whence = Cwd::cwd();
    
    my $util = $self->{UTIL};
    my $output_dir = $self->{OUTPUT_DIR};
    
    chdir "$output_dir" or $util->error("chdir $output_dir failed");
    
    $util->invoke("doxygen");
    chdir "$whence" or $util->error("chdir $whence failed");
}

#
# Generate the Doxygen configuration file.
#
sub make_doc_src()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));
    
    my $major = shift;
    my $minor = shift;
    my $buildnum = shift;

    my $target_name = $self->{TARGET_NAME};
    my $output_dir = $self->{OUTPUT_DIR};
    
    my $util = $self->{UTIL};
    
    $util->info("Generating Doxygen configuration files\n");
    
    open(DOXYFILE, "> ${output_dir}/Doxyfile") or 
	$util->error("Could not open Doxyfile!");
    
    print DOXYFILE <<_____VERBATIM_____;
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
PROJECT_NAME		   = $target_name
PROJECT_NUMBER		   = "V${major}.${minor}.${buildnum}"
OUTPUT_DIRECTORY	   = ./
DETAILS_AT_TOP		   = YES
TAB_SIZE		   = 8
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL		   = YES
EXTRACT_STATIC		   = YES
CASE_SENSE_NAMES	   = YES
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT			   = ${target_name}.cpp
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML		   = YES
GENERATE_TREEVIEW	   = YES
TREEVIEW_WIDTH		   = 250
#---------------------------------------------------------------------------
# configuration options related to the LATEX output
#---------------------------------------------------------------------------
GENERATE_LATEX		   = NO
_____VERBATIM_____

	close DOXYFILE;
}

#
# Generate .pkg file.
#
sub make_pkg_src()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $util = $self->{UTIL};
    my $pkgfile = $self->{PKGFILE};
    my $platform = $self->{PLATFORM};
    my $variant = $self->{VARIANT};
    
    my $major = shift;
    my $minor = shift;
    my $buildnum = shift;
    my $suid = shift;
    my $localised_vendor = shift;
    my $non_localised_vendor = shift;
    
    $util->detailed("Generating SIS .pkg file\n");
    
    my $output_dir = $self->{OUTPUT_DIR};
    my $target_name = $self->{TARGET_NAME};
    my $install_drive = $self->{INSTALL_DRIVE};

    $pkgfile = "$output_dir/${target_name}.pkg";

    if (not $major and $minor and $buildnum)
    {
	$util->warning("Major Minor and build number not specifed.");
    }
    
    $util->detailed("Using Major Minor and build number ",
		    "{$major, $minor, $buildnum} respectively\n");
    
    open(PKGFILEWRITE, "> $pkgfile") or $util->error("Could not open $pkgfile!");
    
    print PKGFILEWRITE <<_____VERBATIM_____;
;Languages
&EN

; Header

; The application\'s major and minor version numbers are required for
; version control (e.g., AppName 3.1 specifies a major build 3, and
; minor build 1.)

; The build-number replaces the variant value which was unimplemented
; in previous versions of makesis.

; All SIS files require a UID, even if the installed components do not
; strictly require one.

; The UID value should be unique for SISAPP (SA) types of SIS file

#{"${target_name}-EN"}, ($suid), $major, $minor, $buildnum, TYPE=SA

;FILERUN, RUNINSTALL
"\\epoc32\\RELEASE\\$platform\\$variant\\${target_name}.exe" - 
"$install_drive:\\sys\\bin\\${target_name}.exe", FR, RI

; Localised vendor
%{"$localised_vendor"}

; Non-localised vendor
:"$non_localised_vendor"
_____VERBATIM_____

	close PKGFILEWRITE;
}

#
# Clean up generated files.
#
sub clean($)
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $reallyclean = (shift @_ > 1);
    
    my $platform = $self->{PLATFORM};
    my $variant  = $self->{VARIANT};
    my $target_name = $self->{TARGET_NAME};
    my $output_dir = $self->{OUTPUT_DIR};
    my $util = $self->{UTIL};

    $util->error("Unknown target") if not $target_name;

    $util->info("Attempting to clean for '$target_name'\n");
    
    use Cwd;
    my $whence = Cwd::cwd();

    if ($reallyclean)
    {
		if ($BuildSystemVersion == "2") 
		{
			if (not -e "$output_dir/bld.inf")
			{
				if (not -d $output_dir)
				{
					mkpath($output_dir, 1, 0777);
					-d $output_dir or $self->error("Could not create $output_dir");
				}
				
				## Generate the project files so we can clean for the
				## specifed platform and variant							
				$self->make_make_src();				
			}
			
			$util->info("Cleaning for platform: $platform, variant: $variant\n");
			
			Cwd::cwd() eq "$output_dir" or 
				chdir "$output_dir" or $util->error("chdir $output_dir failed");
			
			my $platformConfig = "$platform"."_"."$variant";
			$util->invoke("sbs -c $platformConfig reallyclean");			
			
			$util->info("System cleaned for platform: $platform, ",
					"variant: $variant\n");
		}
		else
		{
			if (not -e "$output_dir/abld.bat")
			{
				if (not -d $output_dir)
				{
					mkpath($output_dir, 1, 0777);
					-d $output_dir or $self->error("Could not create $output_dir");
				}
						
				## Generate the project files so we can clean for the
				## specifed platform and variant			
				
				$self->make_make_src();
			
				chdir "$output_dir" or $util->error("chdir $output_dir failed");
				$util->invoke("bldmake bldfiles");
			}
			
			$util->info("Cleaning for platform: $platform, variant: $variant\n");
			
			Cwd::cwd() eq "$output_dir" or 
				chdir "$output_dir" or $util->error("chdir $output_dir failed");

			$util->invoke("abld reallyclean $platform $variant");
			$util->invoke("bldmake clean");
			
			$util->info("System cleaned for platform: $platform, ",
					"variant: $variant\n");
		}
   	}
    
    if (-d $output_dir)
    {
	$util->info("Removing $output_dir\n");
	
	my $root = $self->{ROOT};
	chdir $root or $util->error("chdir $root failed");
	
	use File::Path;
	
	my $deleted_count = rmtree($output_dir, $util->diagnostic(), 1);
	
	$util->detailed("Deleted $deleted_count files\n");
	
	$util->error("Couldn't remove $output_dir") if -d $output_dir;
    }

    chdir "$whence" or $util->error("chdir $whence failed");
}

#
# Generate .SIS file.
#
sub make_sis()
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));
    
    my $util = $self->{UTIL};
    my $output_dir = $self->{OUTPUT_DIR};
    my $target_name = $self->{TARGET_NAME};

    $util->info("Generating sis file\n");
    
    my $args = "";
    
    $args = "-v" if ($util->diagnostic());
    
    use Cwd;
    my $whence = Cwd::cwd();
    
    my $root = $self->{ROOT};
    chdir $root or $util->error("chdir $root failed");

    my $sis_file = "${output_dir}/${target_name}";

    my $pkg_file = $self->{PKGFILE};
    $util->invoke("makesis $args $pkg_file ${sis_file}.unsigned.SIS");
    
    my $key = shift;
    my $certificate = shift;
    my $passphrase = shift;
    
    $util->invoke("signsis", "-s $args", "${sis_file}.unsigned.SIS",	 
		  "${sis_file}.SIS", "$certificate", "$key", "$passphrase");
    
    $util->invoke("signsis", "-o", "${sis_file}.SIS")
	if ($util->level() >= $util->info());

    chdir "$whence" or $util->error("chdir $whence failed");
}

#
# Generate the make and project files.
#
sub make_make_src
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    my $util = $self->{UTIL};
    my $output_dir = $self->{OUTPUT_DIR};
    my $target_name = $self->{TARGET_NAME};

    $util->info("Generating project files\n");

    open(BLDFILEWRITE, "> ${output_dir}/bld.inf") or 
	$util->error("Could not open bld.inf!");
    
    print BLDFILEWRITE <<_____VERBATIM_____;
// Project files
prj_mmpfiles
${target_name}.mmp
_____VERBATIM_____

	close BLDFILEWRITE;

    open(MMPFILEWRITE, "> ${output_dir}/${target_name}.mmp") or 
	$util->error("Could not open ${output_dir}/${target_name}.mmp!");
    
    print MMPFILEWRITE <<_____VERBATIM_____;
CAPABILITY	  TCB
TARGET		  ${target_name}.exe
TARGETTYPE	  exe
UID		  0x1000007A $self->{UID}
_____VERBATIM_____

if (defined($self->{VID}))
{
    print MMPFILEWRITE <<_____VERBATIM_____;
VENDORID	  $self->{VID}
_____VERBATIM_____
}

    print MMPFILEWRITE <<_____VERBATIM_____;
SOURCEPATH	  .
SOURCE		  ${target_name}.cpp
MW_LAYER_SYSTEMINCLUDE_SYMBIAN
OS_LAYER_SYSTEMINCLUDE_SYMBIAN
LIBRARY		  euser.lib
LIBRARY		  EFSRV.lib
LIBRARY		  hash.lib 
LIBRARY		  hal.lib
_____VERBATIM_____

    close MMPFILEWRITE;
}

#
# Generate the application code.
#
sub make_source_src()
{
    my $self = shift;
    die "self not defined" if (not defined($self));

    my $platform = $self->{PLATFORM};
    my $variant  = $self->{VARIANT};
    my $target_name = $self->{TARGET_NAME};
    my $util = $self->{UTIL};
    my $output_dir = $self->{OUTPUT_DIR};

    $util->info("Generating source code\n");

    open(FILEWRITE, "> ${output_dir}/${target_name}.cpp") or 
	log->error("Could not open $target_name!");
    
    my $datentime = $util->timestamp();
    my $storesize = 0;

    print FILEWRITE <<_____VERBATIM_____;
// ${target_name}.cpp
//
// Copyright (c) 2005 Symbian Ltd. All rights reserved.
//

/** \@file ${target_name}.cpp
* Deliver an update to the writable SwiCertstore on a device.
*
* The certificate store is embedded in the executable.
*
* This source code was generated on $datentime
* by SwiCertStoretobin.pl
*/

/** \@mainpage
*
* The Writable SWI certstore forms part of the security subsystem. The
* certificates in the SWI certstore are used by SWIS to perform checks
* when installing software on the device and indirectly by clients of
* the unified certstore.
*
* Updates to the certstore are atomic.	No partial updates to the
* writable certstore are permitted.
*
* The writable SWI certstore will be written to a TCB only writable
* area on the system drive, "\\\\Resource\\\\SwiCertstore\\\\dat\\\\", 
* and will complement the SWI certstore, 
* "Z:\\\\resource\\\\swicertstore.dat", in ROM.
*
* Once the certstore update is installed in the handset, dependent
* processes (processes that have loaded Swicertstore.dll) will each
* synchronise their internal state at the earliest opportunity.
*
* The generated source code varies with the certstore and therefore
* the generated executable cannot be used to deliver other updates to
* the device.  
* 
* $target_name.exe takes no command line options and is not run-time
* configurable in any way.
*/

#include <e32def.h>
#include <e32err.h>
#include <f32file.h>
#include <e32debug.h>
#include <e32property.h>
#include <hash.h>
#include <unifiedcertstore.h>
#include <f32file.h>
#include <hal.h>

namespace 
{
    const TInt KTimeOut(2000000);

_____VERBATIM_____

my $input_file = $self->{INPUT_FILE};
if (-z $input_file)
{
    print FILEWRITE <<_____VERBATIM_____;
    TUint8 *rawdata(0);
_____VERBATIM_____
}
else
{
print FILEWRITE <<_____VERBATIM_____;
    TUint8 rawdata[] = 
    {
_____VERBATIM_____
	    
        open(FILEREAD, "< $input_file") or $util->error("Could not open $input_file!");
	
	binmode FILEREAD;
	
        $storesize = (stat FILEREAD)[7];

	my $byte;
	my $rv;

        $util->detailed("Embedding data.\n");

        my $prefix = $util->prefix();
        $util->prefix('');

	print FILEWRITE "\t";
	for (my $count = 1;; ++$count)
	{
	    $util->detailed(".");
	    
	    # Restrict width of dot pattern
	    $util->detailed("\n") if ($count != 0 and ($count % 70) == 0);
	    
	    read(FILEREAD, $byte, 1) or last;
	    my $v = unpack "C", $byte;
	    
	    if ($v < 0x10)
	    {
		# Aesthetics
		printf FILEWRITE "0x0%x", $v;
	    }
	    else
	    {
		printf FILEWRITE "0x%x", $v;
	    }

	    print FILEWRITE ","	 if ($count != $storesize);
	    print FILEWRITE "\n\t" if (($count % 15) == 0);
	}
	print "\n";
        
        $util->prefix($prefix);

    print FILEWRITE <<_____VERBATIM_____;
    };
_____VERBATIM_____
}

my $digest = $self->{UTIL}->certstore_digest($self->{INPUT_FILE});
my $tmp_name = $self->{UTIL}->tmp_name($target_name, $self->{UID}, $digest);

print FILEWRITE <<_____VERBATIM_____;
	const TInt KRawDataLength($storesize);

	_LIT(KDriveSpec, "!:\\\\");
	_LIT(KSwiCertstoreDatDir, "\\\\Resource\\\\SwiCertstore\\\\dat\\\\");
	_LIT(KSwiCertstoreTmpDir, "\\\\Resource\\\\SwiCertstore\\\\tmp\\\\");
	_LIT(KSwiCertstoreTmpFile, 
	 "\\\\Resource\\\\SwiCertstore\\\\tmp\\\\$tmp_name");
	_LIT8(KSwiCertstoreDigest, "$digest");
	_LIT(KUpdate, "Swicertstore Update error");
}

EXPORT_C TDriveNumber GetSystemDrive()
	{
	TDriveNumber driveNo = RFs::GetSystemDrive();
	return driveNo;
	}

EXPORT_C TChar GetSystemDriveChar(TDriveNumber aDriveNumber)
	{
	TChar driveChar;	
	RFs::DriveToChar(aDriveNumber, driveChar);
	return driveChar;
	}

/**\@brief Ensure that the temporary file is cleaned up if a leave
 *	occurs.
 */ 
void CleanUpTmpFile(TAny *)
	{
	RFs rfssession;

	if (KErrNone != rfssession.Connect())
		{
		return;
		}
    
	TBuf<3> driveSpec(KDriveSpec);
	driveSpec[0] = GetSystemDriveChar(GetSystemDrive());
	rfssession.SetSessionPath(driveSpec);
	
	// Ignore errors
	rfssession.Delete(KSwiCertstoreTmpFile);
	rfssession.Close();
	}

/**\@brief Create the writable SwiCertStore\'s temp and data
 * directories.
 * 
 * \@param aFs A file server session.
 */
void CreateDirsL(RFs &aFs)
	{
	// Create the temp directory where writers ($target_name.exe)
	// will write their temporary files prior to renaming
	
	TInt result(aFs.MkDirAll(KSwiCertstoreTmpDir));
	
	if (KErrAlreadyExists != result and KErrNone != result)
		{
		User::Leave(result);
		}
	
	// Writers ($target_name.exe) will atomically rename (move) their
	// temporary files into KSwiCertstoreDatDir
	
	result = aFs.MkDirAll(KSwiCertstoreDatDir);
	
	if (KErrAlreadyExists != result and KErrNone != result)
		{
		User::Leave(result);
		}
	}

/**\@brief List the contents of the specified directory.
 *
 * \@note The caller is responsible for deleting the memory pointed to
 * by the returned pointer.
 *
 * \@param aFs A file server session.
 * \@param aFn The directory to list.
 *
 * \@return A list of directory entries.
 */
CDir *LsLC(RFs &aFs, const TDesC &aFn)
	{
	CDir *entry_list(0);
	
	User::LeaveIfError(aFs.GetDir(aFn, KEntryAttNormal,
								  ESortByName | EDescending, entry_list));
	
	// The caller is responsible for cleaning up the entry_list
	CleanupStack::PushL(entry_list);
	
	return entry_list;
	}

/**\@brief Get the target filename for the update.
 *
 * The returned filename is generated by incrementing the highest
 * numbered filename in directory
 * "\\\\Resource\\\\SwiCertstore\\\\dat".
 *
 * \@note The caller is responsible for deleting the memory pointed to
 * by the returned pointer.
 *
 * \@param aFs A file server session.
 * \@param aFn A preallocated buffer to hold the filename.
 *
 * \@return A list of directory entries. 
 */
CDir *GetNextFileNameLC(RFs &aFs, TFileName &aFn)
	{
	CDir *entry_list(LsLC(aFs, KSwiCertstoreDatDir));
	
	aFn = KSwiCertstoreDatDir;
	
	TInt number(1);
	
	if (entry_list->Count() > 0)
		{
		const TEntry &item((*entry_list)[0]);
		
		__ASSERT_ALWAYS(EFalse == item.IsDir(), User::Panic(KUpdate,
															KErrCorrupt));
		
		TLex lexer(item.iName);
		const TInt result(lexer.Val(number));
		
		if (KErrNone != result)
			{
			User::Panic(KUpdate, result);
			}
		
		number += 1;
		}
	
	aFn.AppendNumFixedWidth(number, EDecimal, 8);
	
	return entry_list;
	}

/**Convert a hex char to its decimal representation.
 *
 * \@param aCh The char to convert.
 *
 * \@return The integer representation of the supplied char.
 */
TInt ToIntL(const TChar aCh)
	{
	TInt a(-1);
	
	if ((aCh >= 'A') and (aCh <= 'F'))
		{
		a = aCh - 'A' + 10;
		}
	if ((aCh >= 'a') and (aCh <= 'f'))
		{
		a = aCh - 'a' + 10;
		}
	else if (aCh >= '0' and aCh <= '9')
		{
		a = aCh - '0';
		}
	else
		{
		User::Leave(KErrCorrupt);
		}
	
	__ASSERT_ALWAYS((a >= 0 and a < 16), 
			User::Panic(KUpdate, KErrCorrupt)); // four bits
	
	return a;
	}

/**\@brief Convert two hex characters to a single byte.
 *		
 * \@param aChars A pointer to the char sequence to convert.
 *
 * \@param aBuf A reference to a buffer that will contain the
 * binary representation of the char sequence.	The buffer must be be
 * at least half the length of the char sequence.
 */
template <TInt length>
void Hex2BinL(TBuf8<length> &aBuf, TPtrC8 aChars)
	{
	__ASSERT_ALWAYS((aChars.Length() == 2 * length), User::Panic(KUpdate, 
																 KErrArgument));
	__ASSERT_ALWAYS((length % 2 == 0), User::Panic(KUpdate, KErrArgument));
	
	for (int idx(0), appends(0); appends < length; ++appends, idx += 2)
		{			
		aBuf.Append((ToIntL(aChars[idx]) << 4) | ToIntL(aChars[idx + 1]));
		}
	}

/**\@brief Verify the integrity of the writable cerstore on disk.
 *
 * \@param aFs A file server session.
 * \@param aName The name of the file to verify.
 */
void VerifyL(RFs &aFs, const TDesC &aName)
	{
	RFile file;

	User::LeaveIfError(file.Open(aFs, aName,
								 EFileRead | EFileShareExclusive));
	CleanupClosePushL(file);

	TBuf8<1024> block;
	
	CMD5* md5 = CMD5::NewL();
	CleanupStack::PushL(md5);
	
	for (;;)
		{
		User::LeaveIfError(file.Read(block));
		
		if (block.Size() > 0)
			{
			md5->Update(block);
			continue;
			}
		
		break;
		}
	
	TBuf8<MD5_HASH> hash(md5->Final());
	CleanupStack::PopAndDestroy(md5);
	
	TBuf8<MD5_HASH> referencehash;
	
	TPtrC8 digest(KSwiCertstoreDigest);
	
	// Convert the hex digest to binary.  Convert each hex character
	// to four bits
	Hex2BinL(referencehash, digest);
	
	if (referencehash != hash)
		{
		User::Leave(KErrCorrupt);
		}
	
	CleanupStack::PopAndDestroy(); // file
	}

/**\@brief Start the writable SwiCertstore update.
 * 
 * The update is atomic.
 */
void StartL(void)
	{
	TPtr8 data(rawdata, KRawDataLength, KRawDataLength);
	
	RFs rfssession;
	
	User::LeaveIfError(rfssession.Connect());
	CleanupClosePushL(rfssession);
		
	TBuf<3> driveSpec(KDriveSpec);
	driveSpec[0] = GetSystemDriveChar(GetSystemDrive());
	User::LeaveIfError(rfssession.SetSessionPath(driveSpec));		
	
	CreateDirsL(rfssession);
	
	// Ensure that the tmp file is cleaned up if a leave occurs
	CleanupStack::PushL(TCleanupItem(CleanUpTmpFile, 0));
	
	RFile file;	
	
	// If the temp file already exists we will overwrite it.  UIDs are
	// unique after all.
	User::LeaveIfError(file.Replace(rfssession, KSwiCertstoreTmpFile, 
									EFileWrite | EFileShareExclusive));
	
	CleanupClosePushL(file);

	// write is not atomic
	User::LeaveIfError(file.Write(data, KRawDataLength));
	
	User::LeaveIfError(file.Flush());
	CleanupStack::PopAndDestroy(); // file
	
	VerifyL(rfssession, KSwiCertstoreTmpFile);
	
	TFileName fullpath;
	
	CDir *entry_list(0);
	for (;;)
		{
		entry_list = GetNextFileNameLC(rfssession, fullpath);
		
		CleanupStack::Check(entry_list);
		
		// RFs::Rename is atomic for non-removeable media even in the
		// event of power failure
		TInt result(rfssession.Rename(KSwiCertstoreTmpFile, fullpath));
	
		if (KErrInUse == result or KErrAlreadyExists == result)
			{
			// It might be that another writer wrote fullpath first.
			// We will need to scan the directory again
			CleanupStack::PopAndDestroy(entry_list);
			continue;
			}
		
		if (KErrNone == result)
			{
			// entry_list remains on the cleanup stack and is used
			// later
			CleanupStack::Check(entry_list); 
			break;
			}
		
		User::Leave(result);
		}
	
	// We can now notify all parties that are interested in the
	// update.
	const TInt result(RProperty::Set(KUnifiedCertStorePropertyCat, 
									 EUnifiedCertStoreFlag, 1));

	// If property has not been defined then no one is interested in
	// the update
	
	if (KErrNotFound != result and KErrNone != result)
		{
		User::Leave(result);
		}
	
	// Wait for an arbitrary amount of time, in the hope that readers
	// will have closed any other files in KSwiCertstoreDatDir,
	// before attempting to delete all files in KSwiCertstoreDatDir
	// apart from the update we have just placed there.
	
	User::After(KTimeOut);
	
	for (TInt idx(0), sz(entry_list->Count()); idx < sz; ++idx)
		{
		const TEntry &item((*entry_list)[idx]);
		
		fullpath = KSwiCertstoreDatDir;
		fullpath.Append(item.iName);
	
		// Ignore errors - delete as much as possible.	 It may be
		// that a legacy file is currently being read (KErrInUse)
		rfssession.Delete(fullpath);
		}
	
	CleanupStack::PopAndDestroy(entry_list);
	
	// We pop this here because it is now on the top of the stack.
	// It could in fact has been dealt with following a successful
	// rename except that the cleanupitem was not on the top of the
	// stack at that point.
	CleanupStack::Pop(); // cleanupfile
	
	// Now delete any temporary files in the temp directory. There
	// may be other simultaneous update attempts in which case we may
	// be about to defenestrate these.	 This race condition is
	// considered acceptable since there is no agreed protocol
	// between writers and we must clean up ealier failed updates
	// attempts at some point.
	entry_list = LsLC(rfssession, KSwiCertstoreTmpDir);
	
	for (TInt idx(0), sz(entry_list->Count()); idx < sz; ++idx)
		{
		const TEntry &item((*entry_list)[idx]);
		
		fullpath = KSwiCertstoreTmpDir;
		fullpath.Append(item.iName);
	
		// Ignore errors - delete as much as possible.	 It may be
		// that a legacy file is currently being read (KErrInUse)
		rfssession.Delete(fullpath);
		}
	
	
	CleanupStack::PopAndDestroy(2, &rfssession);
	}

/**\@brief Main.
 *
 * \@return A status code.
 */
TInt E32Main(void)
	// No arguments are permitted
	{
	__UHEAP_MARK;
	
	const CTrapCleanup *const stack(CTrapCleanup::New());
	TInt result(KErrNoMemory);
	
	if (stack)
		{
		TRAP(result, StartL());
		
		if (KErrNone != result)
			{
			// Register with the task scheduler and try again later? 
			}
		
		delete stack;
		}
	
	__UHEAP_MARKEND;

	return result;
	}

_____VERBATIM_____
    
    close FILEWRITE;
}

#
# Set the logging level
#
sub level
{
    my $self = shift;
    die "self not defined\n" if (not defined($self));

    if (@_) { $self->{LEVEL} = shift }
    return $self->{LEVEL};
}

1;  # so the require or use succeeds

