installationservices/swtransactionservices/source/server/journal.cpp
author hgs
Wed, 20 Oct 2010 14:52:56 +0300
changeset 80 9dcba1ee99f7
parent 24 84a16765cd86
permissions -rw-r--r--
201041

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


/**
 @file 
 @released
 @internalTechnology
*/

#include "journal.h"
#include "journalfile.h"
#include "operationfunctions.h"
#include "usiflog.h"
#include <f32file.h>

using namespace Usif;

CJournal* CJournal::NewL(RFs& aFs, RLoader& aLoader, TStsTransactionId aTransactionID, const TDesC& aPath)
	{
	CJournal* self = CJournal::NewLC(aFs, aLoader, aTransactionID, aPath);
	CleanupStack::Pop(self);
	return self;
	}

CJournal* CJournal::NewLC(RFs& aFs, RLoader& aLoader, TStsTransactionId aTransactionID, const TDesC& aPath)
	{
	CJournal* self = new(ELeave) CJournal(aFs, aLoader);
	CleanupStack::PushL(self);
	self->ConstructL(aTransactionID, aPath);
	return self;
	}

CJournal::~CJournal()
	{
	iCompletedDrives.Reset();
	iAllDrives.Reset();
	iJournalFiles.ResetAndDestroy();
	}

CJournal::CJournal(RFs& aFs, RLoader& aLoader) : iFs(aFs), iLoader(aLoader)
	{
	}

void CJournal::ConstructL(TStsTransactionId aTransactionID, const TDesC& aPath)
	{
	// construct the generic journal filename (does not include a drive)
	CJournal::CreateJournalFileNameL(aTransactionID, aPath, iJournalFileName);
	// construct the filename for the drives journal (located on the system drive)
	CJournal::CreateDrvFileNameL(aTransactionID, aPath, iDriveArrayFileName);	
	InitJournalsL();
	}

void CJournal::InitJournalsL()
	{	
	RefreshDrivesArrayL();
	
	// attempt to read the journals from all drives
	
	TInt drivesCount(iAllDrives.Count());
	RBuf journal;
	journal.CreateL(KMaxFileName);
	CleanupClosePushL(journal);
	for (TInt i = 0; i < drivesCount; ++i)
		{
		TDriveUnit drive(iAllDrives[i]);
		journal = drive.Name();
		journal.Append(iJournalFileName);

		// do not load completed drives
		if (iCompletedDrives.Find(drive) != KErrNotFound)
			{
			continue;
			}
		
		CJournalFile* journalFile = NULL;
		TRAPD(err, journalFile = CJournalFile::NewL(iFs, iLoader, journal, iAllDrives[i]));
		
		// ignore error'd drives. These will either not be rolled back,
		// or the user will find out later we can't write or commit to them.
		
		if (err == KErrNone)
			{
			CleanupStack::PushL(journalFile);
			iJournalFiles.AppendL(journalFile);
			CleanupStack::Pop(journalFile);
			}
		}
	CleanupStack::PopAndDestroy(&journal);
	}

TInt CJournal::FindJournalFileIndexL(TInt aDrive) const
	{
	for(TInt index = 0; index < iJournalFiles.Count(); ++index)
		if (iJournalFiles[index]->Drive() == aDrive)
			return index;
		
	return KErrNotFound;
	}

void CJournal::DeleteJournalFilesL()
	{
	// delete all journal files
	for(TInt drive = 0; drive < iJournalFiles.Count(); drive++)
		{		
		DeleteJournalFileL(iJournalFiles[drive]->Drive());		
		}	
	iJournalFiles.ResetAndDestroy();
	
	// delete the drive array only after all journals have been completed
	// (committed or rolled back)
	if (iAllDrives.Count() == iCompletedDrives.Count())
		{
		DeleteDrivesFileL();
		}
	}

void VerifyDirectoryDeletionErrorL(TInt err)
	{
	if(err != KErrNone && err != KErrNotFound && err != KErrPathNotFound && err != KErrInUse && err != KErrAccessDenied)
		{
		User::Leave(err);
		}	
	}


void CJournal::DeleteJournalFileL(TInt aDrive, TBool aRecordAllRollbackEvents /* = ETrue */)
	{	
	TInt index = FindJournalFileIndexL(aDrive);
	iJournalFiles[index]->Close();
	TDriveUnit journalDrive(aDrive);
	RBuf journal;
	journal.CreateL(journalDrive.Name(), KMaxFileName);
	CleanupClosePushL(journal);
	journal.Append(iJournalFileName);

	User::LeaveIfError(iFs.Delete(journal));

	// record that we have completed this drive
	if (aRecordAllRollbackEvents)
		UpdateDrivesFileL(aDrive);
		
	iCompletedDrives.InsertInOrder(aDrive);
	
	VerifyDirectoryDeletionErrorL(iFs.RmDir(journal));
	
	CleanupStack::PopAndDestroy(&journal);
	}
	
void CJournal::DeleteDrivesFileL()
	{
	iCompletedDrives.Reset();
	TInt err = KErrNone;
	
	err = iFs.Delete(iDriveArrayFileName);
			
	if(err != KErrNone && err != KErrPathNotFound && err != KErrNotFound)
		{
		User::Leave(err);
		}
    
    // try removing the journal path
	TParse directory;
	User::LeaveIfError(directory.SetNoWild(iDriveArrayFileName, NULL, NULL));
	while(!directory.IsRoot())
		{
		// try to remove this directory
		TInt err = iFs.RmDir(directory.DriveAndPath());
		if(err != KErrNone)
			{
			VerifyDirectoryDeletionErrorL(err);
			break;
			}
		User::LeaveIfError(directory.PopDir());
		}
	}

// This function parses all the drives registered in the main drive file for this transaction.
// This function also checks which drives have been completed.
// As a result, two main drive sets: iCompletedDrives and iAllDrives are populated.
void CJournal::RefreshDrivesArrayL()
	{
	// clear existing journal drive arrays prior to reloading them from file
	iCompletedDrives.Reset();
	iAllDrives.Reset();
	
	RFileReadStream journalStream;
	TInt err = journalStream.Open(iFs, iDriveArrayFileName, EFileStream);
	if (err == KErrNotFound || err == KErrPathNotFound)
		return;
	User::LeaveIfError(err);

	CleanupClosePushL(journalStream);
	while(ETrue)
		{
		// read the next entry in the drives' file
		TInt drive = 0;
		TRAP(err, drive = journalStream.ReadInt32L());
		if (err == KErrEof)
			break;
		
		User::LeaveIfError(err);

		if(iAllDrives.Find(drive) == KErrNotFound)
			{
			// first instance indicates this drive was part of this
			// transaction
			iAllDrives.InsertInOrder(drive);
			}
		else
			{
			// second instance indicates this drive has been committed
			// or rolled back
			iCompletedDrives.InsertInOrder(drive);
			}
		}
	CleanupStack::PopAndDestroy(&journalStream);
	}

void CJournal::UpdateDrivesFileL(TInt aDrive)
	{
	RFile file;
	CleanupClosePushL(file);
	// try opening the file if it already exists
	TInt err = file.Open(iFs, iDriveArrayFileName, EFileWrite);
	if (err != KErrNone)
		{
		if (err == KErrNotFound || err == KErrPathNotFound)
			{
			err = iFs.MkDirAll(iDriveArrayFileName);
			if(err != KErrNone && err != KErrAlreadyExists)
				{
				User::Leave(err);
				}
			// journal does not exist, try creating one
			User::LeaveIfError(file.Create(iFs, iDriveArrayFileName, EFileWrite));
			}
		else
			{
			User::Leave(err);
			}
		}
	
	TInt fileSize;
	User::LeaveIfError(file.Size(fileSize));
	
	// attach to end of file for writing
	RFileWriteStream stream;
	stream.Attach(file, fileSize);	
	CleanupStack::Pop(&file); // file ownership
	CleanupClosePushL(stream);// transfered to stream
	stream.WriteInt32L(aDrive);
	CleanupStack::PopAndDestroy(&stream);
	}

void CJournal::StartCommitL()
	{
	DEBUG_PRINTF3(_L("CJournal::StartCommitL() - iJournalFileName %S   iDriveArrayFileName %S"), &iJournalFileName, &iDriveArrayFileName);							
	// To commit, all drives must exist. No exceptions.
	if (iJournalFiles.Count() != iAllDrives.Count())
		{
		User::Leave(KErrNotReady);
		}
		
	// none of the drives must yet be completed...
	if (iCompletedDrives.Count() != 0)
		{
		User::Leave(KErrNotSupported);
		}
		
	// synch up all the drives
	TInt drivesCount(iJournalFiles.Count());
	for (TInt i = 0; i < drivesCount; ++i)
		{
 		//Check if Journal drives are present.
 		TDriveInfo info;
 		if (iFs.Drive(info, iJournalFiles[i]->Drive())!=KErrNone || info.iType==EMediaNotPresent)
 			{
 			User::Leave(KErrNotReady);	
 			}
		}		
	}

void CJournal::FinishCommitL()
	{
	DEBUG_PRINTF3(_L("CJournal::FinishCommitL() - iJournalFileName %S   iDriveArrayFileName %S"), &iJournalFileName, &iDriveArrayFileName);							
	DeleteJournalFilesL();
	
	// return the journal to a state where it can be used again,
	// in the insane event that someone wants to...
	// (I'm looking at you sisregistry)
	iCompletedDrives.Reset();
	iAllDrives.Reset();
	}

void CJournal::FinishRollbackL(TInt aDrive, TBool aRecordAllRollbackEvents /* = ETrue */)
	{
	DEBUG_PRINTF4(_L("CJournal::FinishRollbackL() - iJournalFileName %S   iDriveArrayFileName %S   aDrive %d"), &iJournalFileName, &iDriveArrayFileName, aDrive);
	DeleteJournalFileL(aDrive, aRecordAllRollbackEvents);
	
	if (iCompletedDrives.Count() == iAllDrives.Count())
		{
		DeleteDrivesFileL();
		}
	}

// This function verifies whether a journal exists for this drive in this transaction.
// If it doesn't, then the journal file is created and added to iJournalFiles
TInt CJournal::PrepareToWriteL(TInt aDrive)
	{	
	TInt index = FindJournalFileIndexL(aDrive);
	if (index >= 0) // The journal already exists
		{
		return index;
		}
	
	__ASSERT_ALWAYS(index == KErrNotFound, User::Invariant());
	// The journal does not exist - we need to create one
	
	TDriveUnit drive(aDrive);
	RBuf journalPath;
	journalPath.CreateL(drive.Name(), KMaxFileName);
	CleanupClosePushL(journalPath);
	journalPath.Append(iJournalFileName);

	CJournalFile* journalFile = CJournalFile::NewLC(iFs, iLoader, journalPath, aDrive);
	iJournalFiles.AppendL(journalFile);
	CleanupStack::Pop(journalFile);
	CleanupStack::PopAndDestroy(&journalPath);
	
	UpdateDrivesFileL(aDrive);
	
	User::LeaveIfError(iAllDrives.InsertInOrder(aDrive));

	return iJournalFiles.Count() - 1; // Since we appended the entry, we return the last index
	}
	
void CJournal::DeleteFilesL(TIntegrityServicesEvent aTypeFilter)
	{
	TInt journalsCount(iJournalFiles.Count());
	for (TInt i = 0; i < journalsCount; ++i)
		{
		iJournalFiles[i]->JournalOperationL(IntegrityDeleteFileL, aTypeFilter, CIntegrityServices::EFailDeletingFile);
		}
	}
	
void CJournal::RestoreFilesL(TInt aDrive)
	{
	TInt index = FindJournalFileIndexL(aDrive);
	User::LeaveIfError(index);
	iJournalFiles[index]->JournalOperationL(IntegrityRestoreFileL, ERemovedFile, CIntegrityServices::EFailRestoringFile);
	}
		
void CJournal::DeleteFilesL(TIntegrityServicesEvent aTypeFilter, TInt aDrive)
	{
	TInt index = FindJournalFileIndexL(aDrive);
	User::LeaveIfError(index);
	iJournalFiles[index]->JournalOperationL(IntegrityDeleteFileL, aTypeFilter, CIntegrityServices::EFailDeletingFile);
	}

void CJournal::WriteJournalEventL(TIntegrityServicesEvent aEvent)
	{
	// write the event to each journal file
	for(TInt index = 0; index < iJournalFiles.Count(); index++)
		{
		iJournalFiles[index]->EventL(aEvent);
		}
	}
	
void CJournal::WriteJournalEventL(TIntegrityServicesEvent aEvent, TInt aDrive, TBool aSerializeEventToJournal)
	{
	TInt index = FindJournalFileIndexL(aDrive);
	User::LeaveIfError(index);
	iJournalFiles[index]->EventL(aEvent, aSerializeEventToJournal);
	}

void CJournal::AddL(const TDesC& aFileName)
	{
	DEBUG_PRINTF2(_L("CJournal::AddL() - aFileName %S"), &aFileName);							
	// write the filename to the journal on the same drive
	TInt drive = CJournalFile::CheckFileNameL(iFs, aFileName);
	TInt index = PrepareToWriteL(drive);
	iJournalFiles[index]->AddL(aFileName);
	}

void CJournal::RemoveL(const TDesC& aFileName, TDes& backupFileName)
	{
	DEBUG_PRINTF2(_L("CJournal::RemoveL() - aFileName %S"), &aFileName);							
	// write the filename to the journal on the same drive
	TInt drive = CJournalFile::CheckFileNameL(iFs, aFileName);
	TInt index = PrepareToWriteL(drive);
	iJournalFiles[index]->RemoveL(aFileName, backupFileName);
	}

void CJournal::TemporaryL(const TDesC& aFileName)
	{
	DEBUG_PRINTF2(_L("CJournal::TemporaryL() - aFileName %S"), &aFileName);							
	// write the filename to the journal on the same drive
	TInt drive = CJournalFile::CheckFileNameL(iFs, aFileName);
	TInt index = PrepareToWriteL(drive);
	iJournalFiles[index]->TemporaryL(aFileName);
	}

TIntegrityServicesEvent CJournal::LastEventL() const
	{
	TIntegrityServicesEvent lastEvent = ENone;
	// work out the "real" last event
	// journals states may be at different since they cannot all be written
	// to simultaneously
	for(TInt index = 0; index < iJournalFiles.Count(); index++)
		{
		TInt position = iCompletedDrives.Find(iJournalFiles[index]->Drive());
		
		if(position!=KErrNotFound) 
			continue; //don't check completed drives
		
		TIntegrityServicesEvent currentJournalLastEvent = iJournalFiles[index]->LastEvent();
		if (currentJournalLastEvent >= lastEvent)
			lastEvent = currentJournalLastEvent;
		}//for
	return lastEvent;
	}
	
TIntegrityServicesEvent CJournal::LastEventL(TInt aDrive) const
	{
	DEBUG_PRINTF2(_L("CJournal::LastEventL() - aDrive %d"), aDrive);							
	TInt index = FindJournalFileIndexL(aDrive);
	User::LeaveIfError(index);

	return iJournalFiles[index]->LastEvent();
	}

/*static*/ void CJournal::CreateDrvFileNameL(TStsTransactionId aTransactionID, const TDesC& aPath, TDes& aDrvFileName)
	{
	DEBUG_PRINTF3(_L("CJournal::CreateDrvFileNameL() - aTransactionID %X aPath %S"), aTransactionID, &aPath);							
	aDrvFileName = TDriveUnit(::RFs::GetSystemDrive()).Name();
	aDrvFileName.Append(aPath);
	aDrvFileName.AppendNumUC(static_cast<TUint32>(aTransactionID), EHex);
	aDrvFileName.Append(KExtDelimiter);
	aDrvFileName.Append(KDriveExt);
	}

/*static*/ void CJournal::CreateJournalFileNameL(TStsTransactionId aTransactionID, const TDesC& aPath, TDes& aJournalFileName)
	{
	aJournalFileName.Append(aPath);
	aJournalFileName.AppendNumUC(static_cast<TUint32>(aTransactionID), EHex);
	aJournalFileName.Append(KExtDelimiter);
	aJournalFileName.Append(KJournalExt);
	}

/*static*/ TInt CJournal::RecoverTransactionIdFromDrvFileName(const TDesC& aDrvFileName, TStsTransactionId& aTransactionID)
	{
	TLex lex(aDrvFileName);
	TUint32 tmp;
	TInt result = lex.Val(tmp, EHex);
	aTransactionID = static_cast<TStsTransactionId>(tmp);
	return result;
	}

void CJournal::RollBackDriveL(TInt aDrive, TBool aRecordAllRollbackEvents)
	{
	switch (LastEventL())
		{
		// Transaction did not complete, rollback required
		case ERemovedFile:
		case EBackupFile:
		case ETempFile:
		case EAddedFile:
		case EAddedFilesRemoved:
		case ERemovedFilesRestored:
			// rollback this individual journal from where it last got to.
			switch (LastEventL(aDrive))
				{
			case ERemovedFile:
			case EBackupFile:
			case ETempFile:
			case EAddedFile:
				DeleteFilesL(EAddedFile, aDrive);
				
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailNewFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);				
				WriteJournalEventL(EAddedFilesRemoved, aDrive, aRecordAllRollbackEvents);
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailNewFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);
				
			// fall-through - automatically proceed to the next state, we'll start from EAddedFilesRemoved in case the RestoreFileL has failed
			case EAddedFilesRemoved:
				RestoreFilesL(aDrive);				
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailOldFilesRestored, CIntegrityServices::EBeforeJournal, KNullDesC);			
				WriteJournalEventL(ERemovedFilesRestored, aDrive, aRecordAllRollbackEvents);
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailOldFilesRestored, CIntegrityServices::EAfterJournal, KNullDesC);

			// fall-through - automatically proceed to the next state
			case ERemovedFilesRestored:				
				DeleteFilesL(ETempFile, aDrive);
				
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);				
				WriteJournalEventL(ETempFilesRemoved, aDrive, aRecordAllRollbackEvents);
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);
				break;
				
			case ETempFilesRemoved:
				break;
		
			// nothing was done, just delete the journal file
			case ENone:
				break;
		
			// Erk! Bad state, bad state!
			default:
				User::Leave(KErrCorrupt);
				break;
				}
			break;
			
		// Transaction complete, just need to remove the backup
		case ECommitted:
		case EBackupFilesRemoved:
			switch (LastEventL(aDrive))
				{
			// At least one journal had a complete transaction...
			// roll forwards all journal files.
			case ERemovedFile:
			case EBackupFile:
			case ETempFile:
			case EAddedFile:
			case ECommitted:
				DeleteFilesL(EBackupFile, aDrive);
			
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailBackupFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);			
				WriteJournalEventL(EBackupFilesRemoved, aDrive, aRecordAllRollbackEvents);
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailBackupFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);

				//fall-through - automatically proceed to the next state
			case EBackupFilesRemoved:				
				DeleteFilesL(ETempFile, aDrive);
			
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);
				WriteJournalEventL(ETempFilesRemoved, aDrive, aRecordAllRollbackEvents);
				CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);
				break;
							
			case ETempFilesRemoved:
				break;
		
			// nothing was done, just delete the journal file
			case ENone:
				break;
		
			// unknown state	
			default:
				User::Leave(KErrCorrupt);
				break;
				}
			break;
			
		case ETempFilesRemoved:
			break;
		
		// nothing was done, just delete the journal file
		case ENone:
			break;
		
		// unknown state	
		default:
			User::Leave(KErrCorrupt);
			break;
		}
		
	FinishRollbackL(aDrive, aRecordAllRollbackEvents);
	}

void CJournal::CommitL()
	{
	StartCommitL();
	
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailInstallComplete, CIntegrityServices::EBeforeJournal, KNullDesC);
	WriteJournalEventL(ECommitted);
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailInstallComplete, CIntegrityServices::EAfterJournal, KNullDesC);
	
	DeleteFilesL(EBackupFile);
	
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailBackupFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);
	WriteJournalEventL(EBackupFilesRemoved);
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailBackupFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);
	
	DeleteFilesL(ETempFile);
	
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EBeforeJournal, KNullDesC);
	WriteJournalEventL(ETempFilesRemoved);
	CIntegrityServices::SimulatePowerFailureL(CIntegrityServices::EFailTempFilesRemoved, CIntegrityServices::EAfterJournal, KNullDesC);
	
	FinishCommitL();
	}

void CJournal::RollBackL(TBool aRecordAllRollbackEvents /*= ETrue*/)
	{
#ifdef __WINSCW__
	// For 2 minutes after initial boot, DLLs are not unloaded. If we are doing a
	// rollback, we need to make sure any pending unloadeds are actioned, otherwise a
	// previously loaded DLL could cause the rollback to fail on windows (on arm it is legal to
	// delete a loaded DLL/EXE, whilst on windows it is not).
	RLoader loader;
	TInt r = loader.Connect();
	if(r == KErrNone)
		{
		(void)loader.CancelLazyDllUnload();
		loader.Close();
		}
#endif
	
	for(TInt index = 0; index < iJournalFiles.Count(); index++)
		{
		TDriveUnit drive = iJournalFiles[index]->Drive();
		// check to see if this drive has already been completed
		if(iCompletedDrives.Find(drive) != KErrNotFound)
			continue;
			
		// only attempt to recover writeable drives that are present
		TDriveInfo info;
		User::LeaveIfError(iFs.Drive(info, drive));
		if (info.iMediaAtt & KMediaAttWriteProtected || info.iType==EMediaNotPresent)
			continue;

		TRAPD(err, RollBackDriveL(drive, aRecordAllRollbackEvents));
		if(err != KErrNone && err != KErrPathNotFound && err != KErrNotFound
			&& err != KErrNotReady)
			{
			// unexpected error
			User::Leave(err);
			}
		}
	}