// Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "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:
//

#include <barsc2.h>
#include <barsread2.h>
#include <e32property.h>
#include <e32uid.h>
#include <e32std.h>
#include <bautils.h>

#include <ssm/ssmcmd.hrh>
#include <ssm/ssmsubstates.hrh>
#ifdef SYMBIAN_SSM_GRACEFUL_SHUTDOWN
#include <ssm/ssmpatchableconstants.h>
#include <ssm/conditiontypes.hrh>
#endif

#include <ssm/ssmstatetransition.h>
#include <ssm/ssmcommandlistresourcereader.h>
#include <ssm/ssmmaxbootattempts_patch.h>

#include "gsastatepolicystartup.h"
#include "ssmdebug.h"
#include "ssmpanic.h" 
#include "s32file.h" 

_LIT(KBootUpFile, ":\\private\\2000d75b\\bootupinfo\\bootupcount.bin");

/**
 Attempt to reboot the device (forever) on boot failure. Used when KSsmMaxBootAttempts is set to '0xFFFFFFFF'.
*/
const TInt KSsmAttemptRebootForever = 0xFFFFFFFF;

/**
Panic used by Startup policy plug-in when resource reader is invalid.
Strings must not be longer than 16 characters or they will be truncated by User::Panic()
*/
_LIT(KPanicGsaStartupState, "StartupPolicy");

/**
 Start-up state policy resource file path format : "z:/private/<SID of SSM>/startup/<Value of KSystemStartupModeKey>/"
*/
_LIT(KCommandListPath, "z:\\private\\2000D75B\\startup\\%d\\");

/**
 Commandlist path to launch 'sysstart.exe' when a resource file for 'start-up' is not found.
 fallback to 'sysstart.exe' resource file path format : "z:/private/<SID of SSM>/startup/fallback/"
*/
_LIT(KFallbackCmdListPath, "z:\\private\\2000D75B\\startup\\fallback\\");

/**
Used to create an instance of MSsmStatePolicy class.

@return A pointer to an instance of MSsmStatePolicy
*/
EXPORT_C MSsmStatePolicy* CGsaStatePolicyStartup::NewL()
	{
	CGsaStatePolicyStartup* self = new (ELeave) CGsaStatePolicyStartup;
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

/**
Gets the hardware reason for KSystemStartupModeKey and makes a RFs connection.
Creates Command list path and the resource reader for startup.

@leave One of the error value returned by 
	    RProperty::Get() 
	    RFs::Connect() 
	    RArray::AppendL() 
	    NewL()
@see RProperty::Get
*/
void CGsaStatePolicyStartup::ConstructL()
	{
	// Read the hardware reason
	User::LeaveIfError(RProperty::Get(KUidSystemCategory, KSystemStartupModeKey, iHardwareReason));
	User::LeaveIfError(iFs.Connect());
	
	// Add supported transitions from Startup 'ESsmStartup'
	iCurrentlySupportedTransitions.AppendL(TSsmState(ESsmFail, KSsmAnySubState));
	iCurrentlySupportedTransitions.AppendL(TSsmState(ESsmShutdown, KSsmAnySubState));
	iCurrentlySupportedTransitions.AppendL(TSsmState(ESsmShutdown, ESsmShutdownSubStateCritical));

	TFileName cmdListPath;
	GetCommandListPath(iHardwareReason, cmdListPath);
	DEBUGPRINT2(_L("Startup Policy : Startup command list path : %S"), &cmdListPath);

	// create resource reader
	iCommandListResourceReader = CSsmCommandListResourceReader::NewL(iFs, cmdListPath, *this);	
	}

/**
default CTOR
*/
CGsaStatePolicyStartup::CGsaStatePolicyStartup()
	{
	}

/**
DTOR
*/
CGsaStatePolicyStartup::~CGsaStatePolicyStartup()
	{
	delete iCommandListResourceReader;
	iSubStates.Close();
	iFs.Close();
	iCurrentlySupportedTransitions.Close();
	}

/**
Initializes command list resource reader.

@param aStatus to complete when the initialization operation has finished
@panic EInvalidResourceReader if the command list resource reader is invalid

@see MSsmStatePolicy::Initialize
*/
void CGsaStatePolicyStartup::Initialize(TRequestStatus& aStatus)
	{
	__ASSERT_DEBUG(iCommandListResourceReader, PanicNow(KPanicGsaStartupState, EInvalidResourceReader));
	
	// initialise command list resource reader.
	iCommandListResourceReader->Initialise(aStatus);
	}

/**
Cancels an asynchronous Initialize operation.

@see MSsmStatePolicy::InitializeCancel
*/
void CGsaStatePolicyStartup::InitializeCancel()
	{
	iCommandListResourceReader->InitialiseCancel();
	}

/** 
Deletes all resources and frees itself.

@see MSsmStatePolicy::Release
*/
void CGsaStatePolicyStartup::Release()
	{
	delete this;
	}

/** 
Determines if an incoming startup state transition request should be accepted or rejected.
Clients calling this API should posess 'ECapabilityPowerMgmt', else the API will return ENotAllowed.

@param aRequest Contains information about the new request
@param aCurrent Contains NULL or the first accepted but not yet completed transition request
@param aQueued Contains NULL or a second accepted but not yet started transition request
@param aMessage Message sent by SSM server, used to check if the client has 'ECapabilityPowerMgmt'

@return one of the TResponse value
@see MSsmStatePolicy::TransitionAllowed
@see MSsmStatePolicy::TResponse
*/
MSsmStatePolicy::TResponse CGsaStatePolicyStartup::TransitionAllowed(const TSsmStateTransition& aRequest, TSsmStateTransition const* aCurrent, 
																TSsmStateTransition const* aQueued, const RMessagePtr2& aMessage)
	{
	TResponse response = ENotAllowed;
	if (!aMessage.HasCapability(ECapabilityPowerMgmt))
		{
		DEBUGPRINT1(_L ("Startup Policy : Capability Check Failed."));
		return response;
		}

	//Check if the requested transition is supported from current state
	if(TransitionSupported(aRequest.State()))
		{
		if((NULL == aCurrent) && (NULL == aQueued))
			{
			// SsmServer is idle
			response = EDefinitelyAllowed;
			}
		else if((aRequest.State().MainState() == ESsmFail) || (aRequest.State().MainState() == ESsmShutdown))
			{
			// Going into failed state or shutdown state will override anything currently ongoing or queued
			response = EReplaceCurrentClearQueue;
			}
		}

#ifdef _DEBUG
	TSsmStateName name = aRequest.State().Name();
	if(ENotAllowed == response)
		{
		DEBUGPRINT3(_L("Startup Policy : Transition (Requested State: %S) is not allowed (Response: %d)."), &name, response);
		}
	else
		{
		DEBUGPRINT3(_L("Startup Policy : Transition (Requested State %S) is allowed (Response %d)."), &name, response);		
		}
#endif
	return response;
	}

/** 
Create the command list associated with a sub state transition.

@param aState Contains the state and substate that identifies the command list to create
@param aReason Contains the reason as given by the request
@param aStatus to complete when the operation has finished
@panic EInvalidResourceReader if the command list resource reader is invalid
@see MSsmStatePolicy::PrepareCommandList
*/ 
void CGsaStatePolicyStartup::PrepareCommandList(TSsmState aState, TInt aReason, TRequestStatus& aStatus)
	{
	__ASSERT_DEBUG(iCommandListResourceReader, PanicNow(KPanicGsaStartupState, EInvalidResourceReader));

	iSoftwareReason = aReason;

	//Let's start from the beginning if no specific minor state is selected
	iRequestedSubState = ((aState.SubState() == KSsmAnySubState) ? ESsmStartupSubStateCriticalStatic : aState.SubState());
	TSsmState publishState;
	publishState.Set(aState.MainState(), iRequestedSubState);

	const TInt commandListId = publishState.SubState();

	//Build the commandlist from resource
	iCommandListResourceReader->PrepareCommandList(commandListId, publishState, aStatus);
	} //lint !e1746 Suppress parameter 'aState' could be made const reference

/**
Cancels asynchronous PrepareCommandList operation.

@see MSsmStatePolicy::PrepareCommandListCancel
*/
void CGsaStatePolicyStartup::PrepareCommandListCancel()
	{
	iCommandListResourceReader->PrepareCommandListCancel();
	}

/**	
Return the command list once the  PrepareCommandList has completed.
Ownership of the returned command list is transferred to the caller.
@panic EInvalidResourceReader if the command list resource reader is invalid
@return The command list created during the preceding PrepareCommandList step
*/
CSsmCommandList* CGsaStatePolicyStartup::CommandList()
	{
	__ASSERT_DEBUG(iCommandListResourceReader , PanicNow(KPanicGsaStartupState, EInvalidResourceReader));

	return iCommandListResourceReader->GetCommandList();
	}

/**
Determines the next sub state transition.
@param aCurrentTransition Contains the last executed state
@param aReason Contains the reason as given by the request
@param aError Contains the completion code from the last executed sub-state transition
@param aSeverity Contains the severity of the failed command in case the sub-state transition ended with an error
@param aNextState The next System State to head for, if there is one
@panic EInvalidStartupstate if the current state is not startup
@return 	ETrue if aNextState contains another System State to head for, or 
		EFalse if there is no further transitions to do.
@see MSsmStatePolicy::GetNextState
*/
TBool CGsaStatePolicyStartup::GetNextState(TSsmState aCurrentTransition, TInt /*aReason*/, TInt aError, TInt /*aSeverity*/, TSsmState& aNextState)
	{
	__ASSERT_ALWAYS(aCurrentTransition.MainState() == ESsmStartup, PanicNow(KPanicGsaStartupState, EInvalidStartupState));

	if (KErrNone != aError)	// Handle CLE error here
		{
		if (iLaunchSysStart)	// 'sysstart.exe' was launched unsuccessfully so launch 'sysagt2srv.exe' and 'wserv.exe'
			{
			iLaunchSysStart = EFalse;
			iLaunchSysAgt2SrvAndWServ = ETrue;
			DEBUGPRINT2(_L("Startup Policy : sysstart.exe launched with error : %d"), aError);
			aNextState = TSsmState(ESsmStartup, ESsmStartupSubStateCriticalDynamic);
			return ETrue;
			}

#ifdef __WINS__	// on emulator
			{
			DEBUGPRINT2(_L("Startup Policy : CLE returned with (error : %d), Panic on Emulator"), aError);
			DEBUGPRINT1(_L("Startup Policy : Emulator (__WINS__) does not support a re-start, so Fail Policy is not invoked."));
			PanicNow(KPanicGsaStartupState, EEmulatorPowerOff);
			}
#else	// on hardware/device
			{
			aNextState = TSsmState(ESsmFail, ESsmFailSubStateRestart);
			if (KSsmAttemptRebootForever != KSsmMaxBootAttempts)
				{
				// Get number of boot attempts made till now from bootup log file
				TInt bootCount = -1;
				TRAPD(err, bootCount = GetBootupCountL());	// ignore failure and restart the device, we should get the value next time.
				if (err!=KErrNone)
					{
					DEBUGPRINT2(_L("Startup Policy : GetBootupCountL() failed with (error: %d), error is deliberately ignored."), aError);
					}
				if (bootCount < KSsmMaxBootAttempts)
					{
					aNextState = TSsmState(ESsmFail, ESsmFailSubStateRestart);
					}
				else	// Maximum allowed boot attempts has been made. Device needs a poweroff. Probable candidate for a reset/reflash.
					{
					aNextState = TSsmState(ESsmFail, ESsmFailSubStatePowerOff);
					}
				}
	#ifdef _DEBUG
			TSsmStateName name = aNextState.Name();
			DEBUGPRINT3(_L("Startup Policy : CLE returned with (error : %d) so moving to Fail State : %S."), aError, &name);
	#endif
			return ETrue;
			}
#endif
		}
	else if(iLaunchSysStart || iLaunchSysAgt2SrvAndWServ)	// If either sysstart or sysagt2srv and wserv was launched and CLE did not return an error
		{
		if (iLaunchSysStart)
			{
			iLaunchSysStart = EFalse;
			DEBUGPRINT1(_L("Startup Policy : sysstart.exe launched successfully."));
			}
		if (iLaunchSysAgt2SrvAndWServ)
			{
			iLaunchSysAgt2SrvAndWServ = EFalse;
			DEBUGPRINT1(_L("Startup Policy : sysagt2srv.exe and wserv.exe launched successfully."));
			}
		aNextState = TSsmState(ESsmNormal, KSsmAnySubState);	// move to Normal state
		return ETrue;
		}
	else	// have to move one with the next substates in this state
		{
		// Get the sub states from resource reader only once
		if (!iSubStatesCount)
			{
			// Get sub states list from resource reader
			TRAPD(err, iCommandListResourceReader->GetCommandListIdsL(iSubStates));
			if (err)
				{
				DEBUGPRINT2(_L("Startup Policy : Command list ids prepared with error: %d"), err);
				}
			else
				{
				iSubStatesCount = iSubStates.Count();
				}
			}

		TInt index = iSubStates.Find(iRequestedSubState);

		if (KErrNotFound == index)
			{
			DEBUGPRINT2(_L("Startup Policy : SubState for transition not found: %d"), index);
			PanicNow(KPanicGsaStartupState, ESubStateIndexNotFound);
			}
		else if (index == (iSubStatesCount - 1))	// transition complete, move to Normal state
			{
			TInt retVal = EFalse;
			// moving to next state as the transition is completed for ESsmStartup
			if (iSubStatesCount && (iRequestedSubState == iSubStates[iSubStatesCount-1]))
				{
				aNextState = TSsmState(ESsmNormal, KSsmAnySubState);
				retVal = ETrue;
				}
			return retVal;
			}
		else		// there is a substate available for transition, moved ahead
			{
			iRequestedSubState = iSubStates[++index];
			aNextState = TSsmState(ESsmStartup, iRequestedSubState);
#ifdef _DEBUG
			TSsmStateName name = aNextState.Name();
			DEBUGPRINT2(_L("Startup Policy : Transition to next state is : %S"), &name);
#endif				
			return ETrue;
			}
		}
	return EFalse;
	} //lint !e1746 Suppress parameter 'aCurrentTransition' could be made const reference

/**
Callback used by CSsmCommandListResourceReader when a decision needs to be made
on whether to include a command in a command list or not.

@param aResourceFile Instance of CResourceFile
@param aResourceId Resource id of SSM_SYMBIAN_CONDITIONAL_INFORMATION struct for command
@return ETrue in case the command needs to be included in command list, else EFalse.

@see MSsmConditionalCallback::ConditionalCommandAllowedL
*/
#ifdef SYMBIAN_SSM_GRACEFUL_SHUTDOWN 
TBool CGsaStatePolicyStartup::ConditionalCommandAllowedL(CResourceFile& aResourceFile, TInt aResourceId)
#else
TBool CGsaStatePolicyStartup::ConditionalCommandAllowedL(CResourceFile& /*aResourceFile*/, TInt /*aResourceId*/)
#endif
	{
	TBool isAllowed = EFalse;        
#ifdef SYMBIAN_SSM_GRACEFUL_SHUTDOWN
   	HBufC8* buf = aResourceFile.AllocReadLC(aResourceId);
   	TResourceReader reader; 
	reader.SetBuffer(buf);
	//Read the type of the command from the resource file
	TUint16 type = reader.ReadUint16();
	CleanupStack::PopAndDestroy(buf);
	// check that the type is equal to "EGracefulShutdown"
	if(type == EGracefulShutdown)    
		{
		//Check whether SSM graceful shutdown is enabled or not
		if(IsSsmGracefulShutdown())   
			{
			isAllowed = ETrue;
			}
		}
#else
	// no commands use 'conditional_information' in Startup state command list.
	PanicNow(KPanicGsaStartupState, EConditionalInfoNotImplemented);
#endif
	return isAllowed;
	}

/*
Helper function to check whether requested transition is supported or not.
*/
TBool CGsaStatePolicyStartup::TransitionSupported(const TSsmState& aRequestedState) const
	{
	return (iCurrentlySupportedTransitions.Find(aRequestedState) > KErrNotFound);
	}

/*
Helper function to create command list path for start-up.
Implements fallback mechanism to launch 'sysstart.exe' if static command list for 'start-up' state is not found.
This temporary implementation is required during migration from existing 'sysstart' to 'ssma start-up'.
*/
void CGsaStatePolicyStartup::GetCommandListPath(TUint aBootMode, TDes& aCmdListPath)
	{
	aCmdListPath.Format(KCommandListPath, aBootMode);
	TBool found = BaflUtils::FolderExists(iFs, aCmdListPath);

	// This Fallback mechanism is used internally during migration from 'sysstart' to 'ssma start-up' and is not required otherwise.
	if (!found)
		{
		aCmdListPath.Copy(KFallbackCmdListPath());
		iLaunchSysStart = ETrue;	// launch 'sysstart.exe'
		}
	}

/*
Helper function to get the boot count
*/
TInt CGsaStatePolicyStartup::GetBootupCountL()
	{
	RBuf bootupInfoPath;
	CleanupClosePushL(bootupInfoPath);
	const TChar drive = RFs::GetSystemDriveChar();
	TInt length = KBootUpFile().Length() + 1; /* for RFs::GetSystemDriveChar() */
	bootupInfoPath.CreateL(length);
	bootupInfoPath.Append(drive);
	bootupInfoPath.Append(KBootUpFile());
	TBool found = BaflUtils::FileExists(iFs, bootupInfoPath);
 	if(!found)
 		{
		User::Leave(EBootupCountFileNotFound);
		}

	RFileReadStream file;
	CleanupClosePushL(file);
	User::LeaveIfError(file.Open(iFs, bootupInfoPath, EFileRead));
	TInt bootCount = file.ReadUint8L();
	CleanupStack::PopAndDestroy(&file);
	CleanupStack::PopAndDestroy(&bootupInfoPath);
	return bootCount;
	}
	
