diff -r aba6b8104af3 -r 84a16765cd86 installationservices/swtransactionservices/source/server/integrityservices.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/installationservices/swtransactionservices/source/server/integrityservices.cpp Fri Mar 19 09:33:35 2010 +0200 @@ -0,0 +1,428 @@ +/* +* 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 + +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& 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 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; iRollBackL(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); + }