installationservices/swtransactionservices/source/server/integrityservices.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 19 Mar 2010 09:33:35 +0200
changeset 24 84a16765cd86
child 42 d17dc5398051
permissions -rw-r--r--
Revision: 201007 Kit: 201011

/*
* Copyright (c) 2008-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: 
* CIntegrityServices implementation
*
*/


/**
 @file 
 @released
 @internalTechnology
*/

#include "integrityservices.h"
#include "journal.h"
#include "operationfunctions.h"
#include "usiflog.h"

#include <f32file.h>

using namespace Usif;

_LIT(KTransactionPath, "\\sys\\install\\integrityservices\\");

CIntegrityServices::TFailType CIntegrityServices::iFailType = EFailNone;
CIntegrityServices::TFailPosition CIntegrityServices::iFailPosition = EBeforeJournal;
TFileName CIntegrityServices::iFailFileName;
TBool CIntegrityServices::iIsFailureTestingEnabled = EFalse;


/**
 * This is a trivial C class that just encapsulates a TEntryArray object in order to 
 * facilitate its storage on the heap.
 *
 * @released
 * @internalComponent 
 */
class CEntryArray : public CBase
	{
public:
	inline TEntryArray& operator()();

private:
	/**
	 * Container to hold file entries
	 */
	TEntryArray iEntryArray;
	};

inline TEntryArray& CEntryArray::operator()()
	{
	return iEntryArray;
	}

 CIntegrityServices* CIntegrityServices::NewL(TStsTransactionId aTransactionID)
	{
	CIntegrityServices* self = CIntegrityServices::NewLC(aTransactionID);
	CleanupStack::Pop(self);
	return self;
	}

 CIntegrityServices* CIntegrityServices::NewLC(TStsTransactionId aTransactionID)
	{
	CIntegrityServices* self = new(ELeave) CIntegrityServices(aTransactionID);
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

 CIntegrityServices::CIntegrityServices(TStsTransactionId aTransactionID) : iTransactionID(aTransactionID)
	{
	}

 CIntegrityServices::~CIntegrityServices()
	{
	delete iJournal;
	iFs.Close();
	
	iLoader.Close();
	}

 void CIntegrityServices::ConstructL()
	{
	DEBUG_PRINTF2(_L("CIntegrityServices::ConstructL() - Opening session with  Session ID %X."), iTransactionID);
	
	User::LeaveIfError(iFs.Connect());
	User::LeaveIfError(iFs.ShareProtected()); //needed as new RFiles are to be passed back to client's side
	User::LeaveIfError(iLoader.Connect());

	// store the journal path and create the journal
	TParsePtrC pathPtr(KTransactionPath);
	iJournalPath = pathPtr.Path();
	iJournal = CJournal::NewL(iFs, iLoader, iTransactionID, iJournalPath);
	iSystemDrive = ::RFs::GetSystemDrive();
	}
 
const TInt KIntegrityServicesSimulatedBatteryFailure=-10205; 
 
/*static*/ void CIntegrityServices::SimulatePowerFailureL(TFailType aFailType, TFailPosition aFailPosition, const TDesC& aFailFileName)
	{
	if (!iIsFailureTestingEnabled)
		return;
	
	if(iFailType == aFailType && iFailPosition == aFailPosition && iFailFileName == aFailFileName)
		{
		User::Leave(KIntegrityServicesSimulatedBatteryFailure);
		}
	}

/*static*/ void CIntegrityServices::NormalizeDirectoryName(TDes& aFileName)
{
	// Directories are represented in the integrity tree and integrity journal exactly as files,
	// without the trailing slash 
	TInt lastCharPos = aFileName.Length() - 1;
	if ( lastCharPos >= 0 && aFileName[lastCharPos] == KPathDelimiter &&
		 aFileName.Locate(KPathDelimiter) != lastCharPos) // Take care not to remove slash from "c:\" and the like
		{
		aFileName.Delete(lastCharPos, 1);
		}			
}

void CIntegrityServices::CopyToBackupL(const TDesC& aSource, const TDesC& aBackup)
{
	// Copying a file isn't atomic so we create a temporary backup file first
	RBuf backupTmpName;
	backupTmpName.Create(aBackup.Length() + 4);
	CleanupClosePushL(backupTmpName);
	backupTmpName.Copy(aBackup);
	_LIT(KTmpExt, ".tmp");
	backupTmpName.Append(KTmpExt);
	
	// Copying a file is not an atomic operation so add the temporary
	// file to the journal to enable cleanup if a power failure occurs before
	// the rename
	SimulatePowerFailureL(EFailAddingTempFile, EBeforeJournal, backupTmpName);
	RegisterTemporaryL(backupTmpName);
	SimulatePowerFailureL(EFailAddingTempFile, EAfterJournal, backupTmpName);	
		
	CFileMan* fileMan = CFileMan::NewL(iFs);
	CleanupStack::PushL(fileMan);
		
	TInt err = fileMan->Copy(aSource, backupTmpName);
	DEBUG_PRINTF4(_L("CIntegrityServices::CopyToBackupL() - Copying %S to %S, err %d"), &aSource, &backupTmpName, err);
	User::LeaveIfError(err);
	
	// Backup is complete, use RFs::Rename as atomic 'commit' of backup
	err = iFs.Rename(backupTmpName, aBackup);			
	DEBUG_PRINTF2(_L("CIntegrityServices::CopyToBackupL() - Commit backup returned error %d"), err);	
	User::LeaveIfError(err);	
	CleanupStack::PopAndDestroy(2, &backupTmpName); // backupTmpName, fileMan 
	
	//  Now the backup is safe the original can be deleted
	err = iLoader.Delete(aSource);
	DEBUG_PRINTF3(_L("CIntegrityServices::CopyToBackupL() - RLoader::Delete %S returned error %d"), &aSource, err);
	User::LeaveIfError(err);
}


 void CIntegrityServices::RegisterNewL(const TDesC& aFileName)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::RegisterNewL() - Session %X, File: %S."),	iTransactionID, &aFileName);

	HBufC* localFilenameHeap = aFileName.AllocLC();
	TPtr localFilename = localFilenameHeap->Des();
	NormalizeDirectoryName(localFilename); // If it is a directory name, make sure to normalize it
		
	// Record the added file or directory in the journal
	SimulatePowerFailureL(EFailAddingNewFile, EBeforeJournal, aFileName);
	iJournal->AddL(localFilename);
	SimulatePowerFailureL(EFailAddingNewFile, EAfterJournal, aFileName);
	CleanupStack::PopAndDestroy(localFilenameHeap);
	}

 void VerifyMkDirErrorL(TInt err)
	{
	if(err != KErrNone && err != KErrAlreadyExists)
		{
		User::Leave(err);
		}	
	}
 
 void ProcessNewFileRegistrationResultL(TInt err, RFs& aFs, const TDesC& aFileName, RFile& aFile)
	{
	if(err!= KErrNone)
		{
		//if we hit this point it means we successfully created the new file however registering with the transaction has  failed
		//so we have to remove the new file to make the journal and the file system consistent
		aFile.Close();
		aFs.Delete(aFileName);
		User::Leave(err);
		}	
	}

 void CIntegrityServices::CreateNewL(const TDesC& aFileName, RFile &newFile, TUint aFileMode)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::CreateNewL() - Session %X, File: %S."),	iTransactionID, &aFileName);
	TInt err = iFs.MkDirAll(aFileName); //first we have to create the full directory path to aFileName otherwise RFs::Create will fail
	VerifyMkDirErrorL(err);
	User::LeaveIfError(newFile.Create(iFs, aFileName, aFileMode));
	TRAPD(regResult, RegisterNewL(aFileName));	
	ProcessNewFileRegistrationResultL(regResult, iFs, aFileName, newFile); //checks if the registration failed and cleans up the file in the filesystem if it did
	}

 void CIntegrityServices::RemoveL(const TDesC& aFileName)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::RemoveL() - Session %X, File: %S."), iTransactionID, &aFileName);

	// before doing anything check that the file or directory exists
	TEntry entry;
	
	TInt res = iFs.Entry(aFileName, entry);
	if (res == KErrNotFound || res == KErrPathNotFound)
		return; // If the file is not present, do nothing. Returning an error would require the user of the API to do additional checks
	User::LeaveIfError(res);

	// We might need to grow this buffer by one byte later
	HBufC* localFilenameHeap = HBufC::NewLC(aFileName.Length() + 1);
	TPtr localFilename = localFilenameHeap->Des();
	localFilename.Copy(aFileName);
	
	TBool isFilenameDir = entry.IsDir();
	// The "if" below is not functionally necessary, but it is a slight optimization - 
	// so that we won't attempt to normalize directory name on files. The optimization is not
	// done in AddL or NormalizeDirectoryName itself, since we don't have future use for TEntry there, and the cost for RFs::Entry overweighs the one for NormalizeDirectoryName
	if ( isFilenameDir ) 
		{
		NormalizeDirectoryName(localFilename);
		}

	RBuf backupFileName;
	backupFileName.CreateL(KMaxFileName);
	CleanupClosePushL(backupFileName);
	SimulatePowerFailureL(EFailRemovingFile, EBeforeJournal, aFileName);
	iJournal->RemoveL(localFilename, backupFileName);
		
	if (backupFileName.Length())
		{
		SimulatePowerFailureL(EFailRemovingFile, EAfterJournal, aFileName);

		TInt err = iFs.MkDirAll(backupFileName);
		VerifyMkDirErrorL(err);

		SimulatePowerFailureL(EFailRemovingFile, EBeforeAction, aFileName);
		_LIT(KSysBinMatch, "?:\\sys\\bin\\*");
		if (localFilename.MatchF(KSysBinMatch) == 0)
		{
			// A copy is slower than a rename to only use the 
			// demand paging safe API for files in sys\bin
			CopyToBackupL(localFilename, backupFileName);			
		}
		else
		{
			err = iFs.Rename(localFilename, backupFileName);
			DEBUG_PRINTF4(_L("CIntegrityServices::RemoveL() - Renamed %S as %S error %d"), &localFilename, &backupFileName, err);
			User::LeaveIfError(err);
		}				
		SimulatePowerFailureL(EFailRemovingFile, EAfterAction, aFileName);
		}
	else
		{
		DEBUG_PRINTF2(_L("CIntegrityServices::RemoveL() - %S already backed up"), &aFileName);
		SimulatePowerFailureL(EFailRemovingFile, EBeforeAction, aFileName);
		// If backupFileName is zero-length, the file was added earlier
		// in the same journal and doesn't need to be backed up.
		if (isFilenameDir)
			{
			CFileMan* fileman = CFileMan::NewL(iFs);
			CleanupStack::PushL(fileman);			
			// Make sure to append slash before calling RmDir - otherwise it deletes the parent directory
			if (localFilename[localFilename.Length()-1] != KPathDelimiter)
	  			{
  				localFilename.Append(KPathDelimiter);
  				}			
			User::LeaveIfError(fileman->RmDir(localFilename)); // A directory cannot be a paged exec., so we don't have to use iLoader
			CleanupStack::PopAndDestroy(fileman);
			}
		else
			{
			User::LeaveIfError(iLoader.Delete(aFileName));
			}
		SimulatePowerFailureL(EFailRemovingFile, EAfterAction, aFileName);			
		}

	// Don't leave an empty directory structure, try pruning it
	RemoveDirectoryTreeL(iFs, aFileName);

	CleanupStack::PopAndDestroy(2, localFilenameHeap); // backupFileName
	}

 void CIntegrityServices::RegisterTemporaryL(const TDesC& aFileName)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::RegisterTemporaryL() - Session %X, File: %S."), iTransactionID, &aFileName);

	// record the temporary file or directory in the journal
	SimulatePowerFailureL(EFailAddingTempFile, EBeforeJournal, aFileName);
	iJournal->TemporaryL(aFileName);
	SimulatePowerFailureL(EFailAddingTempFile, EAfterJournal, aFileName);
	}

 void CIntegrityServices::CreateTemporaryL(const TDesC& aFileName, RFile &newFile, TUint aFileMode)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::CreateTemporaryL() - Session %X, File: %S."), iTransactionID, &aFileName);

	TInt err = iFs.MkDirAll(aFileName); //first we have to create the full directory path to aFileName otherwise RFs::Create will fail
	VerifyMkDirErrorL(err);
	User::LeaveIfError(newFile.Create(iFs, aFileName, aFileMode));
	TRAPD(regResult, RegisterTemporaryL(aFileName));
	ProcessNewFileRegistrationResultL(regResult, iFs, aFileName, newFile);
	}

 void CIntegrityServices::OverwriteL(const TDesC& aFileName, RFile &newFile, TUint aFileMode)
	{
	DEBUG_PRINTF3(_L("CIntegrityServices::OverwriteL() - Session %X, File: %S."), iTransactionID, &aFileName);

	TBool b;
	TInt err;
	if((err=iFs.IsFileOpen(aFileName, b))== KErrNone) //returned error code shows whether the file exists or not; the bool value is ignored
		{
		//file exists remove first
		RemoveL(aFileName);
		}
	else
		{
		if(err != KErrNotFound)
			{
			User::Leave(err);
			}
		}
	CreateNewL(aFileName, newFile, aFileMode);
	}

 void CIntegrityServices::CommitL()
	{
	DEBUG_PRINTF2(_L("CIntegrityServices::CommitL() - Session %X."), iTransactionID);	
	iJournal->CommitL();
	}

 void CIntegrityServices::RollBackL(TBool aRecordAllRollbackEvents /* = ETrue */)
	{
	DEBUG_PRINTF2(_L("CIntegrityServices::RollBackL() - transaction %X"), iTransactionID);
	iJournal->RollBackL(aRecordAllRollbackEvents);
	}

/*static*/ void CIntegrityServices::GetListOfTransactionsL(RArray<TStsTransactionId>& aIdArray)
	{
	RFs fs;
	User::LeaveIfError(fs.Connect());
	CleanupClosePushL(fs);
	TDriveUnit systemDrive(::RFs::GetSystemDrive());
	RBuf fileSpec;
	fileSpec.CreateL(systemDrive.Name(), KMaxFileName);
	CleanupClosePushL(fileSpec);
	fileSpec.Append(KTransactionPath);
	fileSpec.Append(KMatchAny);
	fileSpec.Append(KExtDelimiter);
	fileSpec.Append(KDriveExt);

	RDir dir;
	TInt err = dir.Open(fs, fileSpec, KEntryAttNormal);
	CleanupStack::PopAndDestroy(&fileSpec);
	if (err == KErrPathNotFound || err == KErrNotFound)
		{
		CleanupStack::PopAndDestroy(&fs);
		return; // These errors are not considered fatal - there may be no journals present
		}
	User::LeaveIfError(err);
	CleanupClosePushL(dir);
	
	CEntryArray* entryArrayContainer = new (ELeave) CEntryArray;
	TEntryArray& entryArray = (*entryArrayContainer)();
	err = dir.Read(entryArray);
	CleanupStack::PopAndDestroy(&dir);
	CleanupStack::PushL(entryArrayContainer);	
	if (err != KErrNone && err != KErrEof)
		{
		User::Leave(err);
		}
	TInt entryCount(entryArray.Count());
	for (TInt index = 0; index < entryCount; ++index)
		{
		TStsTransactionId transactionID;
		if (CJournal::RecoverTransactionIdFromDrvFileName(
				entryArray[index].iName, transactionID) == KErrNone)
			{
			aIdArray.AppendL(transactionID);
			}
		}
	CleanupStack::PopAndDestroy(entryArrayContainer);
	CleanupStack::PopAndDestroy(&fs);
	}

/*static*/ void CIntegrityServices::RollbackAllL()
	{
	RArray<TStsTransactionId> transactionIDs;
	CleanupClosePushL(transactionIDs);
	CIntegrityServices::GetListOfTransactionsL(transactionIDs);
	TInt numberOfTransactions(transactionIDs.Count());
	TInt lastError=KErrNone;
	DEBUG_PRINTF2(_L("CIntegrityServices::RollbackAllL() %d transactions have been found."), numberOfTransactions );							
	for(TInt i=0; i<numberOfTransactions; ++i)
		{
		DEBUG_PRINTF2(_L("CIntegrityServices::RollbackAllL() Trying to roll back transaction %X"), transactionIDs[i]);
		TRAPD(err,
			CIntegrityServices* transactionPtr = CIntegrityServices::NewLC(transactionIDs[i]);
			transactionPtr->RollBackL(EFalse); // Specify not to record roll back events. If we failed in the middle of a previous roll back, due to lack of resources we shouldn't be trying to record more events
			CleanupStack::PopAndDestroy(transactionPtr);
			); //failing to roll back one transaction shouldn't affect the overall rollback all procedure
		if(err!=KErrNone)
			{
			lastError=err; //remember last error and leave with that error code indicating an error in the overall procedure
			}
		DEBUG_PRINTF2(_L("CIntegrityServices::RollbackAllL() Rolled back transaction %X"), transactionIDs[i]);			
		}
	CleanupStack::PopAndDestroy(&transactionIDs);
	User::LeaveIfError(lastError);
	}