installationservices/swi/source/integrityservices/integrityservices.cpp
changeset 0 ba25891c3a9e
child 17 741e5bba2bd1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/installationservices/swi/source/integrityservices/integrityservices.cpp	Thu Dec 17 08:51:10 2009 +0200
@@ -0,0 +1,589 @@
+/*
+* 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: 
+* CIntegrityServices implementation
+*
+*/
+
+
+/**
+ @file 
+ @released
+ @internalTechnology
+*/
+
+#include "integrityservices.h"
+#include "journal.h"
+#include "log.h"
+#include "cleanuputils.h"
+#include "operationfunctions.h"
+
+
+#include <f32file.h>
+
+using namespace Swi;
+
+namespace Swi
+{
+/**
+ * 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;
+	}
+} //namespace
+
+EXPORT_C CIntegrityServices* CIntegrityServices::NewL(TInt64 aTransactionID, const TDesC& aPath)
+	{
+	CIntegrityServices* self = CIntegrityServices::NewLC(aTransactionID, aPath);
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+EXPORT_C CIntegrityServices* CIntegrityServices::NewLC(TInt64 aTransactionID, const TDesC& aPath)
+	{
+	CIntegrityServices* self = new(ELeave) CIntegrityServices(aTransactionID);
+	CleanupStack::PushL(self);
+	self->ConstructL(aPath);
+	return self;
+	}
+
+EXPORT_C CIntegrityServices::CIntegrityServices(TInt64 aTransactionID) : iTransactionID(aTransactionID)
+	{
+	}
+
+EXPORT_C CIntegrityServices::~CIntegrityServices()
+	{
+	delete iJournal;
+	iFs.Close();
+	
+	iLoader.Close();
+	}
+
+EXPORT_C void CIntegrityServices::ConstructL(const TDesC& aPath)
+	{
+	DEBUG_PRINTF2(_L8("Integrity Services - Opening session with  Session ID %Ld."), iTransactionID);
+	
+	// check that the supplied path is valid
+	User::LeaveIfError(iFs.Connect());
+	User::LeaveIfError(iFs.IsValidName(aPath));
+	
+	User::LeaveIfError(iLoader.Connect());
+	
+	// store the journal path and create the journal
+	TParsePtrC pathPtr(aPath);
+	iJournalPath = pathPtr.Path();
+	iJournal = CJournal::NewL(iFs, iLoader, iTransactionID, iJournalPath);
+	iSystemDrive = RFs::GetSystemDrive();
+	}
+	
+void CIntegrityServices::SimulatePowerFailureL(TFailType /*aFailType*/, TFailPosition /*aFailPosition*/, const TDesC& /*aFailFileName*/)
+	{
+	// only implemented in derived test class
+	}
+
+/*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);
+		}			
+}
+
+EXPORT_C void CIntegrityServices::AddL(const TDesC& aFileName)
+	{
+	DEBUG_PRINTF3(_L("Integrity Services - Session %Ld, Adding 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 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);
+	iJournal->TemporaryL(backupTmpName);
+	SimulatePowerFailureL(EFailAddingTempFile, EAfterJournal, backupTmpName);	
+		
+	CFileMan* fileMan = CFileMan::NewL(iFs);
+	CleanupStack::PushL(fileMan);
+		
+	TInt err = fileMan->Copy(aSource, backupTmpName);
+	DEBUG_PRINTF4(_L("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("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("CopyToBackupL: RLoader::Delete %S returned error %d"), &aSource, err);
+	User::LeaveIfError(err);
+	}
+	
+EXPORT_C void CIntegrityServices::RemoveL(const TDesC& aFileName)
+	{
+	DEBUG_PRINTF3(_L("Integrity Services - Session %Ld, Removing File: %S"), iTransactionID, &aFileName);
+
+	// before doing anything check that the file or directory exists
+	TEntry entry;
+	User::LeaveIfError(iFs.Entry(aFileName, entry));
+	
+	// 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);
+		if(err != KErrNone && err != KErrAlreadyExists)
+			{
+			User::Leave(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("Renamed %S as %S error %d"), &localFilename, &backupFileName, err);
+			User::LeaveIfError(err);
+			}				
+		SimulatePowerFailureL(EFailRemovingFile, EAfterAction, aFileName);
+		}
+	else
+		{
+		DEBUG_PRINTF2(_L("%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
+	}
+
+EXPORT_C void CIntegrityServices::TemporaryL(const TDesC& aFileName)
+	{
+	DEBUG_PRINTF3(_L("Integrity Services - Session %Ld, Temporary File: %S."),
+		iTransactionID, &aFileName);
+
+	// record the temporary file or directory in the journal
+	SimulatePowerFailureL(EFailAddingTempFile, EBeforeJournal, aFileName);
+	iJournal->TemporaryL(aFileName);
+	SimulatePowerFailureL(EFailAddingTempFile, EAfterJournal, aFileName);
+	}
+
+EXPORT_C void CIntegrityServices::CommitL()
+	{
+	DEBUG_PRINTF2(_L8("Integrity Services - Commiting Session %Ld."), iTransactionID);
+	iJournal->SynchL();
+	iJournal->StartCommitL();
+	
+	SimulatePowerFailureL(EFailInstallComplete, EBeforeJournal, KNullDesC);
+	iJournal->WriteJournalEventL(EInstallComplete);
+	SimulatePowerFailureL(EFailInstallComplete, EAfterJournal, KNullDesC);
+	
+	DeleteFilesL(*iJournal, EBackupFile);
+	
+	SimulatePowerFailureL(EFailBackupFilesRemoved, EBeforeJournal, KNullDesC);
+	iJournal->WriteJournalEventL(EBackupFilesRemoved);
+	SimulatePowerFailureL(EFailBackupFilesRemoved, EAfterJournal, KNullDesC);
+	
+	DeleteFilesL(*iJournal, ETempFile);
+	
+	SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+	iJournal->WriteJournalEventL(ETempFilesRemoved);
+	SimulatePowerFailureL(EFailTempFilesRemoved, EAfterJournal, KNullDesC);
+	
+	iJournal->FinishCommitL();
+	}
+
+EXPORT_C void CIntegrityServices::RollBackL(TBool aAllTransactions)
+	{
+	DEBUG_PRINTF2(_L8("Integrity Services - Rolling Back Session %Ld."), iTransactionID);
+
+	if (aAllTransactions)
+		{
+		// search for and rollback any transaction drive list
+		TDriveUnit systemDrive(iSystemDrive);
+		RBuf fileSpec;
+		fileSpec.CreateL(systemDrive.Name(), KMaxFileName);
+		CleanupClosePushL(fileSpec);
+		fileSpec.Append(iJournalPath);
+		fileSpec.Append(KMatchAny);
+		fileSpec.Append(KExtDelimiter);
+		fileSpec.Append(KDriveExt);
+
+		RDir dir;
+		TInt err = dir.Open(iFs, fileSpec, KEntryAttNormal);
+		if (err == KErrNone)
+			{
+			CEntryArray* entryArrayContainer = new (ELeave) CEntryArray;
+			CleanupStack::PushL(entryArrayContainer);
+			TEntryArray& entryArray = (*entryArrayContainer)();
+			err = dir.Read(entryArray);
+			dir.Close();
+			if (err != KErrNone && err != KErrEof)
+				{
+				User::Leave(err);
+				}
+			TInt entryCount = entryArray.Count();
+			for(TInt index = 0; index < entryCount; ++index)
+				{
+				// check that the entry is not a directory
+				if(!entryArray[index].IsDir())
+					{
+					TLex lex(entryArray[index].iName);
+					TInt64 transactionID;
+					if(lex.Val(transactionID, EHex) == KErrNone)
+						{
+						CJournal* journal = NULL;
+						TRAP(err, journal = CJournal::NewL(iFs, iLoader, transactionID, iJournalPath));
+						if(err == KErrNone)
+							{
+							CleanupStack::PushL(journal);
+							// start the recovery, do not let corrupt journals prevent
+							// recovery of other journals
+							iStartedJournalRollback = ETrue;
+							TRAP(err, RollBackJournalL(*journal));
+							if(err != KErrNone && err != KErrCorrupt)
+								{
+								// unexpected error
+								User::Leave(err);
+								}
+							CleanupStack::PopAndDestroy(journal);
+							}
+						}
+					}
+				}
+			CleanupStack::PopAndDestroy(entryArrayContainer);
+			}
+		else if(err != KErrPathNotFound && err != KErrNotFound && err != KErrNotReady)
+			{
+			// unexpected error
+			User::Leave(err);
+			}
+		CleanupStack::PopAndDestroy(&fileSpec);
+		}
+	else
+		{
+		// rollback only this transaction
+		TRAPD(err, RollBackJournalL(*iJournal));
+		if(err != KErrNone && err != KErrPathNotFound && err != KErrNotFound
+				&& err != KErrNotReady)
+			{
+			// unexpected error
+			User::Leave(err);
+			}
+		}
+	}
+
+void CIntegrityServices::RollBackJournalL(CJournal& aJournal)
+	{
+	aJournal.SynchL();
+
+#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 < aJournal.Drives().Count(); index++)
+		{
+		// check to see if this drive has already been completed
+		if(aJournal.CompletedDrives().Find(aJournal.Drives()[index]) != KErrNotFound)
+			{
+			continue;
+			}
+			
+		// only attempt to recover writeable drives that are present
+		TDriveInfo info;
+		User::LeaveIfError(iFs.Drive(info, aJournal.Drives()[index]));
+		if (!(info.iMediaAtt & KMediaAttWriteProtected) && info.iType!=EMediaNotPresent)
+			{
+			TRAPD(err, RollBackDriveL(aJournal, aJournal.Drives()[index]));
+			if(err != KErrNone && err != KErrPathNotFound && err != KErrNotFound
+				&& err != KErrNotReady)
+				{
+				// unexpected error
+				User::Leave(err);
+				}
+			}
+		}
+	}
+
+void CIntegrityServices::RollBackDriveL(CJournal& aJournal, TInt aDrive)
+	{
+	aJournal.StartRollbackL(aDrive);
+	
+	DEBUG_PRINTF3(_L8("Integrity Services - Rolling Back drive %d, last event %d."), aDrive, aJournal.LastEvent());
+	switch (aJournal.LastEvent())
+		{
+		// 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 (aJournal.LastEventL(aDrive))
+				{
+			case ERemovedFile:
+			case EBackupFile:
+			case ETempFile:
+			case EAddedFile:
+				DeleteFilesL(aJournal, EAddedFile, aDrive);
+				
+				SimulatePowerFailureL(EFailNewFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(EAddedFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailNewFilesRemoved, EAfterJournal, KNullDesC);
+				
+				RestoreFilesL(aJournal, aDrive);
+				
+				SimulatePowerFailureL(EFailOldFilesRestored, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ERemovedFilesRestored, aDrive);
+				SimulatePowerFailureL(EFailOldFilesRestored, EAfterJournal, KNullDesC);
+				
+				DeleteFilesL(aJournal, ETempFile, aDrive);
+				
+				SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ETempFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailTempFilesRemoved, EAfterJournal, KNullDesC);
+				break;
+			
+			// Transaction did not complete, rollback did not complete, resume rollback
+			case EAddedFilesRemoved:
+				RestoreFilesL(aJournal, aDrive);
+				
+				SimulatePowerFailureL(EFailOldFilesRestored, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ERemovedFilesRestored, aDrive);
+				SimulatePowerFailureL(EFailOldFilesRestored, EAfterJournal, KNullDesC);
+				
+				DeleteFilesL(aJournal, ETempFile, aDrive);
+				
+				SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ETempFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailTempFilesRemoved, EAfterJournal, KNullDesC);
+				break;
+				
+			case ERemovedFilesRestored:
+				DeleteFilesL(aJournal, ETempFile, aDrive);
+				
+				SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ETempFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailTempFilesRemoved, 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 EInstallComplete:
+		case EBackupFilesRemoved:
+			switch (aJournal.LastEventL(aDrive))
+				{
+			// At least one journal had a complete transaction...
+			// roll forwards all journal files.
+			case ERemovedFile:
+			case EBackupFile:
+			case ETempFile:
+			case EAddedFile:
+			case EInstallComplete:
+				DeleteFilesL(aJournal, EBackupFile, aDrive);
+			
+				SimulatePowerFailureL(EFailBackupFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(EBackupFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailBackupFilesRemoved, EAfterJournal, KNullDesC);
+			
+				DeleteFilesL(aJournal, ETempFile, aDrive);
+			
+				SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ETempFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailTempFilesRemoved, EAfterJournal, KNullDesC);
+				break;
+			
+			case EBackupFilesRemoved:
+		 		DeleteFilesL(aJournal, ETempFile, aDrive);
+			
+				SimulatePowerFailureL(EFailTempFilesRemoved, EBeforeJournal, KNullDesC);
+				aJournal.WriteJournalEventL(ETempFilesRemoved, aDrive);
+				SimulatePowerFailureL(EFailTempFilesRemoved, 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;
+		}
+		
+	aJournal.FinishRollbackL(aDrive);
+	}
+
+void CIntegrityServices::RestoreFilesL(CJournal& aJournal, TInt aDrive)
+	{
+	DEBUG_PRINTF2(_L8("Integrity Services - RestoreFilesL drive = %d"), aDrive);
+	if (aDrive == -1)
+		{
+		// This applies to all journals
+		aJournal.RestoreFilesL(ERemovedFile, *this, EFailRestoringFile);
+		}
+	else
+		{
+		// specific drive version
+		aJournal.RestoreFilesL(ERemovedFile, aDrive, *this, EFailRestoringFile);
+		}
+	}
+
+void CIntegrityServices::DeleteFilesL(CJournal& aJournal, TIntegrityServicesEvent aEvent, TInt aDrive)
+	{
+	DEBUG_PRINTF3(_L8("Integrity Services - Delete Files drive = %d, event = %d"), aDrive, aEvent);
+	if (aDrive == -1)
+		{
+		// This applies to all journals
+		aJournal.DeleteFilesL(aEvent, *this, EFailDeletingFile);
+		}
+	else
+		{
+		// specific drive version
+		aJournal.DeleteFilesL(aEvent, aDrive, *this, EFailDeletingFile);
+		}
+	}