diff -r 000000000000 -r a41df078684a userlibandfileserver/fileserver/sfile/sf_file_cache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/userlibandfileserver/fileserver/sfile/sf_file_cache.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -0,0 +1,2460 @@ +// Copyright (c) 2006-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: +// f32\sfile\sf_file_cache.cpp +// +// + +/** + @file + @internalTechnology +*/ + +#include +#include +#include "sf_std.h" +#include +#include +#include +#include +#include "sf_file_cache.h" +#include "sf_cache_man.h" +#include "sf_cache_client.h" +#include "sf_file_cache_defs.h" + + +// disables flushing of stale cachelines before each write +#define LAZY_WRITE + +enum TFileCacheFault + { + ECacheSettingsInitFailed, + ECacheSettingsNotFound, + ECacheSettingGetFailed, + ECacheCodeFault, + ECacheBadOperationIndex, + ENoFileCache, + EFileAlreadyClosed, + EClosingDirtyFile, + ECompletingWriteWithDataRemaining, + EPosBeyondSize, + EMsgAlreadyCompleted, + EBadRetCode, + EInternalError, + ERequestUncancelled, + ELockAlreadyOpen, + EBadSegmentCount, + EReNewingOpenCache, + EClosingUnNamedFile, + EFileNameAlreadyOwned, + EClosedFileHasNoName, + EReOpeningUnNamedFile, + EStartingDirtyAWrongStage, + EUnexpectedReNewLFailure, + EDirtyDataOwnerNull, + EFlushingWithSessionNull, + }; + + +LOCAL_C void Fault(TFileCacheFault aFault) +// +// Report a fault in the file cache +// + { + User::Panic(_L("FSFILECACHE"), aFault); + } + +const TInt KMinSequentialReadsBeforeReadAhead = 3; + +//************************************ +// CFileCache +//************************************ + +inline TBool ReadCachingEnabled(CFileShare& aShare) + { + TUint mode = aShare.iMode; + + TInt fileCacheFlags = TFileCacheSettings::Flags(aShare.File().Drive().DriveNumber()); + + if (((mode & EFileReadBuffered) && (fileCacheFlags & (EFileCacheReadEnabled | EFileCacheReadOn))) || + (((mode & EFileReadDirectIO) == 0) && (fileCacheFlags & EFileCacheReadOn))) + { + return ETrue; + } + else + { + return EFalse; + } + } + +inline TBool WriteCachingEnabled(CFileShare& aShare) + { + TUint mode = aShare.iMode; + + TInt fileCacheFlags = TFileCacheSettings::Flags(aShare.File().Drive().DriveNumber()); + + if ((mode & EFileWrite) == 0) + { + return EFalse; + } + else if (((mode & EFileWriteBuffered) && (fileCacheFlags & (EFileCacheWriteEnabled | EFileCacheWriteOn))) || + (((mode & EFileWriteDirectIO) == 0) && (fileCacheFlags & EFileCacheWriteOn))) + { + return ETrue; + } + else + { + return EFalse; + } + } + +void CFileCache::SetFileCacheFlags(CFileShare& aShare) + { + TInt fileCacheFlags = TFileCacheSettings::Flags(iDriveNum); + + TUint& mode = aShare.iMode; + + // enable/disable read ahead + if (((mode & EFileReadAheadOn) && (fileCacheFlags & (EFileCacheReadAheadEnabled | EFileCacheReadAheadOn))) || + (((mode & EFileReadAheadOff) == 0) && (fileCacheFlags & EFileCacheReadAheadOn))) + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheReadAhead ENABLED")); + mode|= EFileReadAheadOn; + } + else + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheReadAhead disabled")); + mode&= ~EFileReadAheadOn; + } + + // enable/disable read caching + if (ReadCachingEnabled(aShare)) + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheRead ENABLED")); + mode|= EFileReadBuffered; + } + else + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheRead disabled")); + // if read caching is off, turn off read-ahead too + mode&= ~(EFileReadBuffered | EFileReadAheadOn); + } + + // enable/disable write caching + if (WriteCachingEnabled(aShare)) + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheWrite ENABLED")); + mode|= EFileWriteBuffered; + } + else + { + __CACHE_PRINT(_L("CACHEFILE: EFileCacheWrite disabled")); + mode&= ~EFileWriteBuffered; + } + + } + +void CFileCache::ConstructL(CFileShare& aShare) + { + iDrive = &aShare.File().Drive(); + iDriveNum = iDrive->DriveNumber(); + iMount=&iDrive->CurrentMount(); + + DoInitL(iDriveNum); + + + CCacheManager* manager = CCacheManagerFactory::CacheManager(); + + iCacheClient = manager->CreateClientL(); + manager->RegisterClient(*iCacheClient); + + + TInt segmentSize = SegmentSize(); + TInt segmentSizeMask = I64LOW(SegmentSizeMask()); + // Get file cache size + iCacheSize = TFileCacheSettings::CacheSize(iDriveNum); + // must be at least one segment + iCacheSize = Max(iCacheSize, segmentSize); + // round up to nearest whole segment + iCacheSize = (iCacheSize + segmentSize - 1) & segmentSizeMask; + + // Get max read-ahead length + iMaxReadAheadLen = TFileCacheSettings::MaxReadAheadLen(iDriveNum); + // must be at least one segment + iMaxReadAheadLen = Max(iMaxReadAheadLen, segmentSize); + // round up to nearest whole segment + iMaxReadAheadLen = (iMaxReadAheadLen + segmentSize - 1) & segmentSizeMask; + // read-ahead should not be greater than the cache size minus one segment + iMaxReadAheadLen = Min(iMaxReadAheadLen, iCacheSize - segmentSize); + // ... or greater than one cacheline (128K should be enough !) + iMaxReadAheadLen = Min(iMaxReadAheadLen, iCacheClient->CacheLineSize()); + + iFileCacheReadAsync = TFileCacheSettings::FileCacheReadAsync(iDriveNum); + + iClosedFileKeepAliveTime = TFileCacheSettings::ClosedFileKeepAliveTime(iDriveNum); + iDirtyDataFlushTime = TFileCacheSettings::DirtyDataFlushTime(iDriveNum); + + // Calculate max number of segments to cache + TInt maxSegmentsToCache = iCacheSize >> SegmentSizeLog2(); + + __CACHE_PRINT1(_L("CACHEFILE: maxSegmentsToCache %d"), maxSegmentsToCache); + + iCacheClient->SetMaxSegments(maxSegmentsToCache); + + CFileCache* fileCache = ReNewL(aShare); + __ASSERT_ALWAYS(fileCache != NULL, Fault(EUnexpectedReNewLFailure)); + } + +CFileCache* CFileCache::NewL(CFileShare& aShare) + { + __CACHE_PRINT(_L("CACHEFILE: CFileCache::NewL()")); + + CFileCB* file = &aShare.File(); + if ((CCacheManagerFactory::CacheManager() == NULL) || + (!ReadCachingEnabled(aShare) && !WriteCachingEnabled(aShare)) || + (FsThreadManager::IsDriveSync(file->Drive().DriveNumber(),EFalse))) + return NULL; + + CFileCache* fileCache = new(ELeave) CFileCache(); + CleanupClosePushL(*fileCache); + fileCache->ConstructL(aShare); + CleanupStack::Pop(1, fileCache); + return fileCache; + } + +CFileCache* CFileCache::ReNewL(CFileShare& aShare) + { + __CACHE_PRINT(_L("CACHEFILE: CFileCache::ReNewL()")); + + // check not already open i.e. attached to a CFileCB + __ASSERT_DEBUG(iFileCB == NULL, Fault(EReNewingOpenCache)); + + // make sure the drive thread exists (the mount may have been mounted + // synchronously since the drive was last open) + const TInt r = FsThreadManager::GetDriveThread(iDriveNum, &iDriveThread); + if ((r!= KErrNone) || !iDriveThread) + return NULL; + + // if re-opening in DirectIo mode, destroy the file cache + if (!ReadCachingEnabled(aShare) && !WriteCachingEnabled(aShare)) + { + Close(); + return NULL; + } + + SetFileCacheFlags(aShare); + + // assign ownership of this object to aFileCB before any leaves occur + iFileCB = &aShare.File(); + iFileCB->iBody->iFileCache = this; + + __ASSERT_DEBUG(iLock.Handle() == KNullHandle, Fault(ELockAlreadyOpen)); + User::LeaveIfError(iLock.CreateLocal()); + + // delete the file name to save heap space (it's only needed + // while the file is on the closed queue so that it can be matched) + delete iFileNameF; + iFileNameF = NULL; + + return this; + } + +void CFileCache::Init(CFileShare& aShare) + { + SetFileCacheFlags(aShare); + } + +CFileCache::CFileCache() : + iClosedTimer(ClosedTimerEvent, this), + iDirtyTimer(DirtyTimerEvent, this) + { + } + +CFileCache::~CFileCache() + { + iLock.Close(); + + // stop the owning CFileCB from pointing to an about-to-be deleted object + if (iFileCB && iFileCB->iBody) + iFileCB->iBody->iFileCache = NULL; + + CCacheManagerFactory::CacheManager()->DeregisterClient(*iCacheClient); + + delete iCacheClient; + + delete iFileNameF; + } + + +TInt CFileCache::ClosedTimerEvent(TAny* aFileCache) + { + TClosedFileUtils::Remove((CFileCache*) aFileCache); + return KErrNone; + } + +TInt CFileCache::DirtyTimerEvent(TAny* aFileCache) + { + // Cannot report errors here + // coverity [unchecked_value] + (void)((CFileCache*) aFileCache)->FlushDirty(); + + return KErrNone; + } + + + +void CFileCache::Close() + { + __CACHE_PRINT1(_L("CFileCache::Close() 0x%x"),this); + + TInt r = KErrNone; + +#ifdef _DEBUG + if (iCacheClient) // NB Object may not have been fully constructed + { + TInt64 pos; + TUint8* addr; + __ASSERT_DEBUG(((iCacheClient->FindFirstDirtySegment(pos, addr)) == 0), Fault(EClosingDirtyFile)); + __ASSERT_DEBUG(!iDirtyDataOwner, Fault(EClosingDirtyFile)); + } +#endif + + + __CACHE_PRINT2(_L("CFileCache::Close() iFileCB %08X IsClosed %d"), + iFileCB, TClosedFileUtils::IsClosed(this)); + // if not already closed move to closed file queue + if (iFileCB != NULL && + !iFileCB->DeleteOnClose() && + !TClosedFileUtils::IsClosed(this) && + IsDriveThread()) + { + // add to ClosedFiles container + __CACHE_PRINT1(_L("CLOSEDFILES: Adding %S\n"), &iFileCB->FileNameF() ); + TRAP(r, TClosedFileUtils::AddL(this, ETrue)); + + // Acquire ownership of the CFileCB's file name + __ASSERT_DEBUG(iFileCB->iFileNameF, Fault(EClosingUnNamedFile)); + __ASSERT_DEBUG(iFileNameF == NULL, Fault(EFileNameAlreadyOwned)); + iFileNameF = iFileCB->iFileNameF; + iNameHash = iFileCB->iNameHash; + iFileCB->iFileNameF = NULL; + + // remove pointer to owning CFileCB as this is called from CFileCB's destructor + iFileCB = NULL; + + // Successfully moved file ! + if (r == KErrNone) + { + // close the RFastLock object here to prevent OOM kernel tests from failing + iLock.Close(); + return; + } + } + + iClosedTimer.Stop(); + + // if already closed, close for good. N.B. CFsObject::DoClose() will + // cause it to be removed from the ClosedFiles container + CFsDispatchObject::Close(); + } + + +CMountCB& CFileCache::Mount() const + { + return *iMount; + } + +CFileCB* CFileCache::FileCB() + { + return iFileCB; + } + + + +TInt CFileCache::SegmentSize() const + { + return iCacheClient->SegmentSize(); + } + +TInt CFileCache::SegmentSizeLog2() const + { + return iCacheClient->SegmentSizeLog2(); + } + +TInt64 CFileCache::SegmentSizeMask() const + { + return iCacheClient->SegmentSizeMask(); + } + +/** +Sets the cached file size + +@param aSize The size of the file. +*/ +void CFileCache::SetSize64(TInt64 aSize) + { + __e32_atomic_store_ord64(&iSize64, aSize); + } + +TDrive& CFileCache::Drive() const + { + return *iDrive; + } + +TUint32 CFileCache::NameHash() const + { + return(iNameHash); + } + +HBufC& CFileCache::FileNameF() const + { + __ASSERT_DEBUG(iFileNameF, Fault(EClosedFileHasNoName)); + return(*iFileNameF); + } + + + + +TBool CFileCache::IsDriveThread() + { + return FsThreadManager::IsDriveThread(iDriveNum, EFalse); + } + + +void CFileCache::ResetReadAhead() + { +// iSequentialReads = 0; + iReadAheadLen = 0; + iReadAheadPos = 0; + } + +/** +ReadAhead() - +dispatches a new message to fill the read cache if +(ReadAheadPos - ShareReadPos) <= (ReadAheadLen) + +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + ^ ^ + |----- ReadAheadLen -----------------> + | | + ShareReadPos ReadAheadPos + +Every successful read-ahead doubles ReadAheadLen until it reaches the maximum +(KDefaultFileCacheMaxReadAheadLen). +*/ +void CFileCache::ReadAhead(CFsMessageRequest& aMsgRequest, TUint aMode) + { + if (!(aMode & EFileReadAheadOn) || + (iSequentialReads < KMinSequentialReadsBeforeReadAhead) || + iMaxReadAheadLen == 0) + return; + + + TInt segmentSize = SegmentSize(); + TInt64 segmentSizeMask = SegmentSizeMask(); + + // if the read-ahead pos has been reset to zero, then the read-ahead + // position and length must be re-calculated + + TBool resetting = (iReadAheadPos == 0)?(TBool)ETrue:(TBool)EFalse; + if (resetting) + { + iReadAheadPos = (iLastReadPos + segmentSize - 1) & segmentSizeMask; + + // ensure read ahead len at least as big as last read + iReadAheadLen = Max(iReadAheadLen, iLastReadLen); + + // round up to a segment size + iReadAheadLen = (iReadAheadLen + segmentSize - 1) & (TInt) segmentSizeMask; + + // ensure read ahead len at least as big as 1 segments + iReadAheadLen = Max(iReadAheadLen, segmentSize); + + // ensure read ahead len not greater than the maximum read ahead len + iReadAheadLen = Min(iReadAheadLen, iMaxReadAheadLen); + + iReadAheadRequest = NULL; + } + + TInt bytesBuffered = (TInt) (iReadAheadPos - iLastReadPos); + TInt bytesNotBuffered = iMaxReadAheadLen - bytesBuffered; + + + // if read-ahead buffer len > current read-ahead len OR + // read-ahead buffer is more than half full, do nothing + if ((iReadAheadRequest) || + (bytesBuffered > iReadAheadLen) || + (bytesBuffered > (iMaxReadAheadLen>>1)) || + (iReadAheadPos >= iSize64)) + { + return; + } + + // double the read-ahead length - unless this is the first + if (!resetting) + iReadAheadLen<<= 1; + + // ensure read ahead len not greater than the free space available in buffer + iReadAheadLen = Min(iReadAheadLen, bytesNotBuffered); + + // round up to a segment size + iReadAheadLen = (iReadAheadLen + segmentSize - 1) & (TInt) segmentSizeMask; + +#if defined (_DEBUG_READ_AHEAD) + TInt64 oldReadAheadPos = iReadAheadPos; +#endif + + DoReadAhead(aMsgRequest, aMode); + + +// RDebug::Print(_L("Buffered: old %d new %d"), bytesBuffered, (iReadAheadPos == 0)?bytesBuffered:iReadAheadPos - iLastReadPos); + +#if defined (_DEBUG_READ_AHEAD) +RDebug::Print(_L("Buffered: old %d new %d iLastReadPos %d ReadAheadPos old %d new %d iReadAheadLen %d"), + bytesBuffered, + (TInt) ((iReadAheadPos == 0)?bytesBuffered:iReadAheadPos - iLastReadPos), + I64LOW(iLastReadPos), + I64LOW(oldReadAheadPos), + I64LOW(iReadAheadPos), + iReadAheadLen); +#endif // (_DEBUG_READ_AHEAD) + + + return; + } + + +void CFileCache::DoReadAhead(CFsMessageRequest& aMsgRequest, TUint aMode) + { + + + if (iCacheClient->FindSegment(iReadAheadPos) != NULL) + { + // if read ahead pos is already cached, then synchronous reads have caught up, + // so reset read ahead pos. +#if defined (_DEBUG_READ_AHEAD) + RDebug::Print(_L("ReadAhead: pos %d already cached"), I64LOW(iReadAheadPos)); +#endif + iReadAheadPos = 0; + return; + } + + + CFsClientMessageRequest* newRequest = NULL; + TInt r = AllocateRequest(newRequest, EFalse, aMsgRequest.Session()); + if (r != KErrNone) + return; + + r = newRequest->PushOperation( + iReadAheadPos, + iReadAheadLen, + (TUint8*) NULL, + 0, // aOffset + NULL); // aCallback + if (r != KErrNone) + { + newRequest->Free(); + newRequest = NULL; + return; + } + + __CACHE_PRINT2(_L("TFsFileRead: ReadAhead pos %ld len %d"), newRequest->CurrentOperation().iReadWriteArgs.iPos, newRequest->CurrentOperation().iReadWriteArgs.iTotalLength); + +// RDebug::Print(_L("ReadH:\tpos %d\tlen %d"), I64LOW(newRequest->CurrentOperation().iReadWriteArgs.iPos), newRequest->CurrentOperation().iReadWriteArgs.iTotalLength); + + r = ReadBuffered(*newRequest, aMode); + if (r != CFsRequest::EReqActionContinue) + { + // if read ahead pos is already cached, then synchronous reads have caught up, + // so reset read ahead pos. + if (r == CFsRequest::EReqActionComplete) + { +#if defined (_DEBUG_READ_AHEAD) + RDebug::Print(_L("ReadAhead pos %d ALREADY DONE !!!"), I64LOW(iReadAheadPos)); +#endif + iReadAheadPos = 0; + } + + newRequest->PopOperation(); + newRequest->Free(); + newRequest = NULL; + return; + } + + iReadAheadPos = iReadAheadPos + iReadAheadLen; + iReadAheadRequest = newRequest; + +#if defined (_DEBUG_READ_AHEAD) + RDebug::Print(_L("Dispatching ReadAhead with %s priority"), iFileCacheReadAsync?_S16("HIGH"):_S16("LOW")); +#endif + // If if media driver reads are synchronous (i.e. not interrupt driven) dispatch read-ahead + // with low priority so that it doesn't prevent client thread from running + newRequest->Dispatch( + EFalse, // don't init + iFileCacheReadAsync?(TBool)EFalse:(TBool)ETrue, + EFalse); // dispatch to back + } + +TInt CFileCache::CompleteRead(CFsRequest* aRequest) + { + CFsMessageRequest& msgRequest = *(CFsMessageRequest*) aRequest; + + __ASSERT_DEBUG(msgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + + CFileShare* share; + CFileCB* file; + GetFileFromScratch(aRequest, share, file); + CFileCache* fileCache = file->FileCache(); + + __ASSERT_DEBUG(fileCache != NULL, Fault(ENoFileCache)); +#ifdef _DEBUG + TInt cancelling = (msgRequest.LastError() == KErrCancel)?(TBool)ETrue:(TBool)EFalse; +#endif + + TUint mode = share?share->iMode : EFileReadBuffered; + TInt r = fileCache->ReadBuffered(msgRequest, mode); + + // if this request has been cancelled we mustn't dispatch it again - + // we still need to call state machine however so that any locked segments can be unlocked + + + TInt lastError = msgRequest.LastError(); + +#ifdef _DEBUG + __ASSERT_DEBUG(!cancelling || msgRequest.LastError() == KErrCancel, Fault(ERequestUncancelled)); +#endif + + if (lastError == KErrCancel) + r = CFsRequest::EReqActionComplete; + + return r; + } + +/** +ReadBuffered - attempts to read from cache. +Called from TFsFileRead::Initialise() and CFileCache::CompleteRead() + +@return CFsRequest::EReqActionComplete if request entirely satisfied + CFsRequest::EReqActionContinue if request not yet complete. + Results in transition from : + TFsFileRead::PostInitialise() to TFsFileRead::DoRequestL() or + CFileCache::CompleteRead() to TFsFileRead::DoRequestL() + CFsRequest::EReqActionBusy if filecache is "busy" +*/ +TInt CFileCache::ReadBuffered(CFsMessageRequest& aMsgRequest, TUint aMode) + { + iLock.Wait(); + + CFsClientMessageRequest* newRequest = NULL; + + __CACHE_PRINT2(_L("CFileCache::ReadBuffered() pos %d, len %d"), I64LOW(aMsgRequest.CurrentOperation().iReadWriteArgs.iPos), aMsgRequest.CurrentOperation().iReadWriteArgs.iTotalLength); + TInt r = DoReadBuffered(aMsgRequest, aMode, newRequest); + + iLock.Signal(); + + if (newRequest) + newRequest->Dispatch(); + + return r; + } + + +void CFileCache::UpdateSharePosition(CFsMessageRequest& aMsgRequest, TMsgOperation& aCurrentOperation) + { + // update the file share's position if this request came from client + if (aCurrentOperation.iClientRequest) + { + CFileShare* share = (CFileShare*)aMsgRequest.ScratchValue(); + if (aMsgRequest.LastError() == KErrNone) + { + __e32_atomic_store_ord64(&share->iPos, aCurrentOperation.iReadWriteArgs.iPos); + } + } + } + +TInt CFileCache::DoReadBuffered(CFsMessageRequest& aMsgRequest, TUint aMode, CFsClientMessageRequest*& aNewRequest) + { + enum states + { + EStBegin=0, + EStReadFromCache, + EStReadFromDiskComplete, + EStCopyToClient, + EStReStart, + EStEnd + }; + + + TInt retCode = CFsRequest::EReqActionComplete; + TInt& lastError = aMsgRequest.LastError(); + TBool driveThread = IsDriveThread(); + + // temp storage for transition between EStReadFromCache / EStReadFromDiskComplete and EStCopyToClient + TInt readLen = 0; + TUint8* addr = NULL; + TInt64 segmentStartPos = 0; + + TInt segmentSize = SegmentSize(); + TInt segmentSizeLog2 = SegmentSizeLog2(); + TInt64 segmentSizeMask = SegmentSizeMask(); + + for(;;) + { + TMsgOperation* currentOperation = &aMsgRequest.CurrentOperation(); + TInt64& currentPos = currentOperation->iReadWriteArgs.iPos; + TInt& totalLen = currentOperation->iReadWriteArgs.iTotalLength; + TInt& currentOffset = currentOperation->iReadWriteArgs.iOffset; + TBool readAhead = (currentOperation->iReadWriteArgs.iData == NULL)?(TBool)ETrue:(TBool)EFalse; + + switch(currentOperation->iState) + { + case EStBegin: + + + // if EFileReadDirectIO, flush write cache and read direct from disk + if (!(aMode & EFileReadBuffered)) + { + iLock.Signal(); + TInt r = FlushDirty(&aMsgRequest); + iLock.Wait(); + if (r == CFsRequest::EReqActionBusy) + return CFsRequest::EReqActionBusy; + return CFsRequest::EReqActionContinue; // read uncached + } + + if (currentPos > iSize64) + currentPos = iSize64; + + currentOperation->iState = EStReadFromCache; + + // count the number of sequential reads for read-ahead + if (currentOperation->iClientRequest) + { + if (currentPos == iLastReadPos) + { + iSequentialReads++; + } + else + { + iSequentialReads = 0; + ResetReadAhead(); + } + iLastReadPos = currentPos + totalLen; + iLastReadLen = totalLen; + } + + + // fall into... + + case EStReadFromCache: + { + // reading past end of file ? + if (currentPos + totalLen > iSize64) + totalLen = (TInt) (iSize64 - currentPos); + + + if (totalLen == 0 || lastError != KErrNone) + { + currentOperation->iState = EStEnd; + break; + } + + segmentStartPos = currentPos & segmentSizeMask; + + + TInt64 endPos = currentPos + totalLen; + TUint maxLenToRead = (TUint)Min((TInt64)(iCacheClient->CacheLineSize()), (endPos - segmentStartPos)); + TInt maxSegments = (maxLenToRead + segmentSize - 1) >> segmentSizeLog2; + + + TInt segmentCount = maxSegments; + TInt filledSegmentCount; + TInt lockError; + addr = iCacheClient->FindAndLockSegments(segmentStartPos, segmentCount, filledSegmentCount, lockError, EFalse); + + +#if defined (_DEBUG_READ_AHEAD) + if (addr && readAhead) + RDebug::Print(_L("READAHEAD CACHELINE ALREADY EXISTS POS %d"), I64LOW(segmentStartPos)); +#endif + + // if cacheline contains filled and empty segments, deal with these seperately + // to simplify the code..... + if (filledSegmentCount > 0 && segmentCount > filledSegmentCount) + { + segmentCount = Min(segmentCount, filledSegmentCount); + } + + if (lockError == KErrInUse) + { + // cacheline in use (by other thread): + // if this is a read-ahead, abandon it + // otherwise re-post the request + if (readAhead) + { + totalLen = 0; + retCode = CFsRequest::EReqActionComplete; + currentOperation->iState = EStEnd; + } + else + { +#if defined (_DEBUG_READ_AHEAD) + RDebug::Print(_L("READC CACHELINE BUSY POS %d"), I64LOW(segmentStartPos)); +#endif + + return CFsRequest::EReqActionBusy; + } + break; + } + + // if not found, try to allocate as many contiguous segments + // as possible so that the number of reads issued to the media + // driver is kept to a minumum + + if (addr == NULL) // not found ? + { + // if read len > size of the cache, don't read excess + // through the cache as this is wasteful + if (totalLen > iCacheSize) + { + TInt len = totalLen - iCacheSize; + TInt r = aMsgRequest.PushOperation( + currentPos, len, + (TDesC8*) currentOperation->iReadWriteArgs.iData, currentOffset, + CompleteRead, + EStReadFromCache); + if (r != KErrNone) + { + currentOperation->iState = EStReStart; + break; + } + + currentOffset+= len; + currentPos+= len; + totalLen-= len; + return CFsRequest::EReqActionContinue; + } + + // if position not cached postpone Initialise() to drive thread + // as there may be a read ahead already in the queue + + if (!driveThread && !readAhead) + { +#if defined (_DEBUG_READ_AHEAD) + RDebug::Print(_L("*** POSTING READ TO DRIVE THREAD POS %d ***"), I64LOW(segmentStartPos)); +#endif + return CFsRequest::EReqActionBusy; + } + + segmentCount = maxSegments; + addr = iCacheClient->AllocateAndLockSegments(segmentStartPos, segmentCount, EFalse, !readAhead); + if (addr == NULL) + { + __CACHE_PRINT(_L("AllocateSegment failed")); + currentOperation->iState = EStReStart; + break; + } + } + + + readLen = segmentCount << segmentSizeLog2; + __ASSERT_DEBUG(iSize64 > segmentStartPos, Fault(EPosBeyondSize)); + readLen = (TInt)Min((TInt64)readLen, (iSize64 - segmentStartPos)); + + if (iCacheClient->SegmentEmpty(segmentStartPos)) + { + // store readLen & addr in scratch area + currentOperation->iScratchValue0 = addr; + currentOperation->iScratchValue1 = (TAny*) readLen; + + // read into cache segment(s) + __CACHE_PRINT2(_L("CACHEFILE: read pos %ld, readLen %d"), segmentStartPos, readLen); + TInt r = aMsgRequest.PushOperation( + segmentStartPos, readLen, addr, 0, + CompleteRead, + EStReadFromDiskComplete); + if (r != KErrNone) + { + iCacheClient->UnlockSegments(segmentStartPos); + currentOperation->iState = EStReStart; + break; + } + + return CFsRequest::EReqActionContinue; + } + else + { + currentOperation->iState = EStCopyToClient; + } + } + // fall into ... + + case EStCopyToClient: + { + TInt segmentsLocked = (readLen + segmentSize - 1) >> segmentSizeLog2; + + if (lastError == KErrNone) + { + // copy to user buffer + TInt offset = iCacheClient->CacheOffset(currentPos); // offset into segment + TInt len = Min(readLen - offset, totalLen); + + // if addr is NULL then this is a read ahead request + if (currentOperation->iReadWriteArgs.iData != NULL) + { + if (currentOperation->iClientRequest) + { + __ASSERT_DEBUG (aMsgRequest.Message().Handle() != NULL, Fault(EMsgAlreadyCompleted)); + TPtrC8 ptr(addr+offset, len); + lastError = aMsgRequest.Write(0, ptr, currentOffset); + } + else + { + memcpy(((TUint8*) currentOperation->iReadWriteArgs.iData) + currentOffset, addr+offset, len); + } + } + else + { + iReadAheadRequest = NULL; + } + + currentOffset+= len; + currentPos+= len; + totalLen-= len; + } + + if (lastError == KErrNone) + iCacheClient->MarkSegmentsAsFilled(segmentStartPos, segmentsLocked); + iCacheClient->UnlockSegments(segmentStartPos); + + if (lastError != KErrNone) + { + retCode = CFsRequest::EReqActionComplete; + currentOperation->iState = EStEnd; + break; + } + + if (totalLen > 0 && lastError == KErrNone) + { + currentOperation->iState = EStReadFromCache; + break; + } + currentOperation->iState = EStEnd; + } + // fall into ... + + + case EStEnd: + // update the file share's position if this request came from client + UpdateSharePosition(aMsgRequest, *currentOperation); + return retCode; + + case EStReadFromDiskComplete: + { + // restore readLen etc from scratch area + segmentStartPos = currentPos & segmentSizeMask; + addr = (TUint8*) currentOperation->iScratchValue0; + readLen = (TInt) currentOperation->iScratchValue1; + + aMsgRequest.CurrentOperation().iState = EStCopyToClient; + } + break; + + case EStReStart: + + // ignore the failure if this is a read-ahead + if (readAhead) + { + totalLen = 0; + retCode = CFsRequest::EReqActionComplete; + currentOperation->iState = EStEnd; + break; + } + + // We need to flush all dirty data to disk in case this read overlaps + // with any dirty data already in the file cache + if (aMode & EFileWriteBuffered) + { + TInt r = DoFlushDirty(aNewRequest, &aMsgRequest, ETrue); + if (r == CFsRequest::EReqActionBusy || r != CFsRequest::EReqActionComplete) + return r; + } + + retCode = CFsRequest::EReqActionContinue; // read uncached + currentOperation->iState = EStEnd; + + /* + We're now going to by-pass the file cache. + If we've already written something to the client's buffer then, in the flexible + memory model, the KDesWrittenShift bit will be set and so the descriptor length will + updated in RMessageK::CallbackFunc() when the client thread runs. This (shorter) + length will overwrite the descriptor length written by the local media subsystem. + To get round this problem, we set the descriptor length artificially by writing a + zero-length descriptor at the end of the client's buffer. + */ + if (currentOffset > 0 && currentOperation->iClientRequest) + { + __ASSERT_DEBUG (aMsgRequest.Message().Handle() != NULL, Fault(EMsgAlreadyCompleted)); + TPtrC8 ptr(NULL, 0); + TInt r = aMsgRequest.Write(0, ptr, currentOffset+totalLen); + __CACHE_PRINT3(_L("CFileCache::DoReadBuffered() failed at pos %lx offset %x totalLen %x\n"), currentPos, currentOffset, totalLen); + __CACHE_PRINT2(_L("CFileCache::DoReadBuffered() writing zero bytes at offset %x r %d\n"), currentOffset + totalLen, r); + if (r != KErrNone) + retCode = r; + } + + aMsgRequest.ReStart(); + UpdateSharePosition(aMsgRequest, *currentOperation); + return retCode; + + + }; + } // for (;;) + + } + + + +/** +WriteBuffered - attempts to write to cache. +Called from TFsFileRead::Initialise and TFsFileRead::DoRequestL + +@return CFsRequest::EReqActionComplete if request entirely satisfied + CFsRequest::EReqActionContinue if request not yet complete + CFsRequest::EReqActionBusy if filecache is busy +*/ +TInt CFileCache::WriteBuffered(CFsMessageRequest& aMsgRequest, TUint aMode) + { + iLock.Wait(); + + + CFsClientMessageRequest* newRequest = NULL; + + __CACHE_PRINT2(_L("CFileCache::WriteBuffered() pos %d, len %d"), I64LOW(aMsgRequest.CurrentOperation().iReadWriteArgs.iPos), aMsgRequest.CurrentOperation().iReadWriteArgs.iTotalLength); + TInt r = DoWriteBuffered(aMsgRequest, newRequest, aMode); + + iLock.Signal(); + + if (newRequest) + newRequest->Dispatch(); + + // completion ? + if (r == CFsRequest::EReqActionComplete) + { + TMsgOperation& currentOperation = aMsgRequest.CurrentOperation(); + + __ASSERT_DEBUG(aMsgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + + TFsFileWrite::CommonEnd(&aMsgRequest, r, iInitialSize, iSize64, currentOperation.iReadWriteArgs.iPos, EFalse); + } + + return r; + } + + + + +TInt CFileCache::CompleteWrite(CFsRequest* aRequest) + { + CFsMessageRequest& msgRequest = *(CFsMessageRequest*) aRequest; + + CFileShare* share = (CFileShare*)aRequest->ScratchValue(); + CFileCB& file = share->File(); + CFileCache* fileCache = file.FileCache(); //&file; + __ASSERT_DEBUG(fileCache != NULL, Fault(ENoFileCache)); + +#ifdef _DEBUG + TInt cancelling = (msgRequest.LastError() == KErrCancel)?(TBool)ETrue:(TBool)EFalse; +#endif + + + TInt r = fileCache->WriteBuffered(msgRequest, share->iMode); + +#ifdef _DEBUG + __ASSERT_DEBUG(!cancelling || msgRequest.LastError() == KErrCancel, Fault(ERequestUncancelled)); +#endif + + // if this request has been cancelled we mustn't dispatch it again - + // we still need to call state machine however so that any locked segments can be unlocked + TInt lastError = msgRequest.LastError(); + if (lastError == KErrCancel) + r = CFsRequest::EReqActionComplete; + + return r; + } + + + +TInt CFileCache::DoWriteBuffered(CFsMessageRequest& aMsgRequest, CFsClientMessageRequest*& aNewRequest, TUint aMode) + + { + enum states + { + EStBegin=0, + EStWriteToCache, + EStReadFromDisk, + EStReadFromDiskComplete, + EStWriteToCacheComplete, + EStCopyFromClient, + EStReStart, + EStEnd, + EStWriteThrough + }; + + enum flags + { + EReadFirstSegment = 0x01, + EReadLastSegment = 0x02 + }; + + TBool cachingWrites = (aMode & EFileWriteBuffered)?(TBool)ETrue:(TBool)EFalse; + TInt& lastError = aMsgRequest.LastError(); + TBool driveThread = IsDriveThread(); + TInt segmentSize = SegmentSize(); + TInt segmentSizeLog2 = SegmentSizeLog2(); + TInt64 segmentSizeMask = SegmentSizeMask(); + + // temp storage for transition between EStWriteToCache / EStWriteToCacheComplete and EStCopyFromClient + TInt segmentCount = 0; + TUint8* addr = NULL; + TInt64 firstSegmentStartPos = 0; + TInt64 readPos = 0; + TUint8* readAddr = NULL; + TInt readSegmentCount = 0; + + TInt readFlags = 0; + + for(;;) + { + TMsgOperation* currentOperation = &aMsgRequest.CurrentOperation(); + TInt retCode = CFsRequest::EReqActionComplete; + + TInt64& currentPos = currentOperation->iReadWriteArgs.iPos; + TInt& totalLen = currentOperation->iReadWriteArgs.iTotalLength; + TInt& currentOffset = currentOperation->iReadWriteArgs.iOffset; + switch(currentOperation->iState) + { + case EStBegin: + { + // Report background flush errors back to client + CFileShare* share = (CFileShare*) aMsgRequest.ScratchValue(); + if (share->iFlushError != KErrNone) + { + TInt r = share->iFlushError; + share->iFlushError = KErrNone; + return r; + } + + if (currentPos > iSize64) + currentPos = iSize64; + iInitialSize = iSize64; + + // if EFileWriteDirectIO OR + // (caching writes and requested write len > size of the cache), + // flush the write cache + if ((aMode & EFileWriteBuffered) == 0 || + (cachingWrites && totalLen > iCacheSize)) + { + TInt r = DoFlushDirty(aNewRequest, &aMsgRequest, ETrue); + if (r == CFsRequest::EReqActionBusy || r != CFsRequest::EReqActionComplete) + return r; + } + // if EFileWriteDirectIO then overwrite any non-dirty data and + // write direct to disk.... + + // if caching writes and requested write len > size of the cache, + // don't write excess through the cache as this is wasteful + if (cachingWrites && totalLen > iCacheSize) + { + // Destroy ALL cached data (dirty and non-dirty) to ensure + // cache is consistent with data written to disk + iCacheClient->Purge(ETrue); + + TInt len = totalLen - iCacheSize; + TInt r = aMsgRequest.PushOperation( + currentPos, len, + (TDesC8*) currentOperation->iReadWriteArgs.iData, currentOffset, + CompleteWrite, + EStWriteToCache); + if (r != KErrNone) + { + currentOperation->iState = EStReStart; + break; + } + + currentOffset+= len; + currentPos+= len; + totalLen-= len; + return CFsRequest::EReqActionContinue; + } + + currentOperation->iState = EStWriteToCache; + } + break; + + case EStWriteToCache: + __ASSERT_DEBUG(aMsgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + { + // NB we must carry on if we get an error to ensure cache is consistent with disk + if (totalLen == 0 || lastError == KErrCancel) + { + currentOperation->iState = EStWriteToCacheComplete; + break; + } + + firstSegmentStartPos = currentPos & segmentSizeMask; + TInt64 endPos = currentPos + totalLen; + + // Find the maximum number of contiguous segments we need to lock + // in this cacheline - when we unlock these will be marked as filled + const TInt64 dataRange = Min((TInt64)iCacheSize, (endPos + segmentSize - 1 - firstSegmentStartPos)); + TInt maxSegmentCount = (TInt)(dataRange >> segmentSizeLog2); + + segmentCount = maxSegmentCount; + + TInt lockError; + TInt filledSegmentCount; + addr = iCacheClient->FindAndLockSegments(firstSegmentStartPos, segmentCount, filledSegmentCount, lockError, cachingWrites); + + if (lockError == KErrInUse) + return CFsRequest::EReqActionBusy; + +#ifdef LAZY_WRITE + if (cachingWrites && addr == NULL && lastError == KErrNone && iCacheClient->TooManyLockedSegments()) + { + // Flush a single dirty cacheline (if there is one for this file) + // or all dirty data on this drive (if there is any). + // If there's nothing to flush then the dirty (locked) data + // must belong to another drive, so there's not much we can do + // but write through + TInt r = DoFlushDirty(aNewRequest, &aMsgRequest, EFalse); + if (r == CFsRequest::EReqActionBusy) + return CFsRequest::EReqActionBusy; + if (r != KErrInUse) + { + // Need to switch to drive thread to flush all data on this drive + if (!driveThread) + return CFsRequest::EReqActionBusy; + iLock.Signal(); + TInt r = iDrive->FlushCachedFileInfo(); + iLock.Wait(); + if (r == CFsRequest::EReqActionBusy) + return CFsRequest::EReqActionBusy; + + lastError = KErrNoMemory; // write through + } + } +#else + // flush cache before allocating a new cacheline + if (cachingWrites && addr == NULL && lastError == KErrNone) + { + // if no segment available, flush a single dirty cacheline + // or wait if already flushing + if (iFlushBusy) + return CFsRequest::EReqActionBusy; + TInt r = DoFlushDirty(aNewRequest, &aMsgRequest, EFalse); + if (r != CFsRequest::EReqActionBusy && r != CFsRequest::EReqActionComplete) + lastError = r; + } +#endif + // if no cacheline found & write caching is enabled, allocate a new cacheline + if (cachingWrites && addr == NULL && lastError == KErrNone) + { + // try to allocate up to write cache size - this may not + // be possible if a segment in the range is already cached + segmentCount = maxSegmentCount; + addr = iCacheClient->AllocateAndLockSegments(currentPos, segmentCount, cachingWrites, ETrue); + + // continue if alloc failed + if (addr == NULL) + lastError = KErrNoMemory; + + } + + if (addr == NULL) + { + if (cachingWrites && lastError == KErrNone) + lastError = KErrNoMemory; + segmentCount = 1; + currentOperation->iState = EStCopyFromClient; + break; + } + + // if the first or last segment are empty then we'll have to read-before-write + TInt64 lastSegmentStartPos = firstSegmentStartPos + ((segmentCount-1) << segmentSizeLog2); + + TInt startPosSegOffset = iCacheClient->CacheOffset(currentPos); + TInt endPosSegOffset = iCacheClient->CacheOffset(endPos); + + // partial first segment ? + if (startPosSegOffset != 0 && + firstSegmentStartPos < iFileCB->Size64() && + iCacheClient->SegmentEmpty(firstSegmentStartPos)) + { + readFlags|= EReadFirstSegment; + } + + // partial last segment ? + // NB this may be the first segment too ! + if (endPosSegOffset != 0 && + endPos < lastSegmentStartPos + segmentSize && + endPos < iFileCB->Size64() && + iCacheClient->SegmentEmpty(lastSegmentStartPos)) + { + readFlags|= EReadLastSegment; + } + + // read-before-write required ? + if (readFlags & EReadFirstSegment) + { + readFlags&= ~EReadFirstSegment; + readPos = firstSegmentStartPos; + readAddr = addr; + readSegmentCount = 1; + // if the last segment is empty and it's the same as the first or contiguous, + // then read that too + if ((readFlags & EReadLastSegment) && (segmentCount <= 2)) + { + readSegmentCount = segmentCount; + readFlags&= ~EReadLastSegment; + } + currentOperation->iState = EStReadFromDisk; + } + else if (readFlags & EReadLastSegment) + { + readFlags&= ~EReadLastSegment; + readPos = lastSegmentStartPos; + readAddr = addr + ((segmentCount-1) << segmentSizeLog2); + readSegmentCount = 1; + currentOperation->iState = EStReadFromDisk; + } + else + { + currentOperation->iState = EStCopyFromClient; + } + } + break; + + case EStReadFromDisk: + { + // Save address & segmentCount in scratch area + currentOperation->iScratchValue0 = addr; + __ASSERT_DEBUG(segmentCount < 0xFFFF, Fault(EBadSegmentCount)); + currentOperation->iScratchValue1 = (TAny*) ((readFlags << 16) | segmentCount); + + TInt maxReadLen = readSegmentCount << segmentSizeLog2; +#ifdef __VC32__ +#pragma warning( disable : 4244 ) +//Disable Compiler Warning (levels 3 and 4) C4244: 'conversion' conversion from 'type1' to 'type2', possible loss of data +// maxReadLen is of 31 bits. Hence Min result is reduced to 31 bits +#endif + TInt readLen = (TInt)Min((TInt64)maxReadLen, (iSize64 - readPos)); +#ifdef __VC32__ +#pragma warning( default : 4244 ) +#endif + __ASSERT_DEBUG (aMsgRequest.Message().Handle() != NULL, Fault(EMsgAlreadyCompleted)); + TInt r = aMsgRequest.PushOperation( + readPos, readLen, readAddr, 0, + CompleteWrite, + EStReadFromDiskComplete, + EFsFileRead); + if (r != KErrNone) + { + lastError = KErrNoMemory; + currentOperation->iState = EStEnd; + iCacheClient->UnlockSegments(firstSegmentStartPos); + break; + } + + // requeue this request + return CFsRequest::EReqActionContinue; + } + + case EStReadFromDiskComplete: + { + __ASSERT_DEBUG(aMsgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + // restore addr & segmentCount etc from scratch area + firstSegmentStartPos = currentPos & segmentSizeMask; + addr = (TUint8*) currentOperation->iScratchValue0; + segmentCount = ((TInt) currentOperation->iScratchValue1) & 0xFFFF; + readFlags = ((TInt) currentOperation->iScratchValue1) >> 16; + + if (readFlags & EReadLastSegment) + { + TInt64 lastSegmentStartPos = firstSegmentStartPos + ((segmentCount-1) << segmentSizeLog2); + readFlags&= ~EReadLastSegment; + readPos = lastSegmentStartPos; + readAddr = addr + ((segmentCount-1) << segmentSizeLog2); + readSegmentCount = 1; + currentOperation->iState = EStReadFromDisk; + break; + } + + aMsgRequest.CurrentOperation().iState = EStCopyFromClient; + } + break; + + case EStCopyFromClient: + { + TInt writeLen = segmentCount << segmentSizeLog2; + TInt offset = iCacheClient->CacheOffset(currentPos); // offset into segment + writeLen = Min(writeLen - offset, totalLen); + + if (addr != NULL) + { + __ASSERT_DEBUG (aMsgRequest.Message().Handle() != NULL, Fault(EMsgAlreadyCompleted)); + // copy from user buffer to cache buffer + TInt writeError = KErrNone; + if (currentOperation->iClientRequest) + { + TPtr8 ptr(addr+offset, writeLen, writeLen); + writeError = aMsgRequest.Read(0, ptr, currentOffset); + } + else + { + memcpy(addr+offset, ((TUint8*) currentOperation->iReadWriteArgs.iData) + currentOffset, writeLen); + } + + // "writing" past end of file ? + if (currentPos + writeLen > iSize64 && cachingWrites && lastError == KErrNone && writeError == KErrNone) + { + __CACHE_PRINT2(_L("CACHEFILE: extending size old %ld new %ld"), iSize64, currentPos + totalLen); + iSize64 = currentPos + writeLen; + } + + TInt anyError = (writeError != KErrNone)?writeError:lastError; + + if (anyError == KErrNone) + iCacheClient->MarkSegmentsAsFilled(firstSegmentStartPos, segmentCount); + + if (cachingWrites && anyError == KErrNone) + { + iCacheClient->MarkSegmentsAsDirty(firstSegmentStartPos, segmentCount); + // start dirty data timer + FileDirty(aMsgRequest); + } + + // unlock if we're not buffering writes (segments won't be unlocked if dirty) + iCacheClient->UnlockSegments(firstSegmentStartPos); + } + + currentOffset+= writeLen; + currentPos+= writeLen; + totalLen-= writeLen; + + currentOperation->iState = EStWriteToCache; + break; + } + + case EStWriteToCacheComplete: + { + __ASSERT_DEBUG(aMsgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + + if (lastError == KErrCancel) + { + currentOperation->iState = EStEnd; + } + else if ((!cachingWrites) || (lastError != KErrNone)) + { + // allow TFsFileWrite::DoRequestL() to proceed normally using original pos & len + __ASSERT_DEBUG(aMsgRequest.CurrentOperationPtr() != NULL, Fault(ECacheBadOperationIndex)); + + currentOperation->iState = EStWriteThrough; + } + else + { + __CACHE_PRINT2(_L("CACHEFILE: write buffered pos %ld, len %d"), + aMsgRequest.CurrentOperation().iReadWriteArgs.iPos, aMsgRequest.CurrentOperation().iReadWriteArgs.iOffset); + __ASSERT_DEBUG(totalLen == 0, Fault(ECompletingWriteWithDataRemaining)); + + currentOperation->iState = EStEnd; + } + break; + } + + case EStWriteThrough: + + if (lastError == KErrCancel) + { + currentOperation->iState = EStEnd; + break; + } + + // we're going to issue an uncached write so clear any error + lastError = KErrNone; + + if (cachingWrites) + { + TInt r = DoFlushDirty(aNewRequest, &aMsgRequest, ETrue); +#ifdef _DEBUG + if (r == CFsRequest::EReqActionBusy) + CCacheManagerFactory::CacheManager()->Stats().iWriteThroughWithDirtyDataCount++; +#endif + // Need to reset currentOperation.iReadWriteArgs.iTotalLength here to make sure + // TFsFileWrite::PostInitialise() doesn't think there's no data left to process + aMsgRequest.ReStart(); + if (r == CFsRequest::EReqActionBusy || r != CFsRequest::EReqActionComplete) + return r; + } + + retCode = CFsRequest::EReqActionContinue; + + // fall into...EStRestart + currentOperation->iState = EStReStart; + + case EStReStart: + + __ASSERT_DEBUG(retCode == CFsRequest::EReqActionBusy || retCode == CFsRequest::EReqActionContinue, Fault(EBadRetCode)); + + aMsgRequest.ReStart(); + UpdateSharePosition(aMsgRequest, *currentOperation); + if (currentOperation->iClientRequest) + currentOperation->iReadWriteArgs.iPos = currentOperation->iClientPosition; // NB maybe KCurrentPosition64 + return retCode; + + case EStEnd: + return retCode; + } + } + } + + + +TInt CFileCache::AllocateRequest(CFsClientMessageRequest*& aNewRequest, TBool aWrite, CSessionFs* aSession) + { + + RLocalMessage msgNew; + const TOperation& oP = OperationArray[aWrite?EFsFileWriteDirty:EFsFileRead]; + TInt r = RequestAllocator::GetMessageRequest(oP, msgNew, aNewRequest); + if (r != KErrNone) + return r; + + aNewRequest->Set(msgNew, oP, aSession); + aNewRequest->SetDrive(iDrive); + + // read-aheads and write-dirty requests should not be posted to plugins + // If there are data-modifying plugins, then these should sit above the file cache + aNewRequest->iCurrentPlugin = NULL; + aNewRequest->EnablePostIntercept(EFalse); + + // Scratch value is a CFileCB pointer NOT a CFileShare for requests + // allocated by the file cache + aNewRequest->SetScratchValue64( MAKE_TINT64(EFalse, (TUint) iFileCB) ); + + // don't call Initialise(), don't call PostInitialise() + aNewRequest->SetState(CFsRequest::EReqStateDoRequest); + + // don't call Message().Complete() + aNewRequest->SetCompleted(EFalse); + + __ASSERT_DEBUG(aNewRequest->CurrentOperationPtr() == NULL, Fault(EBadOperationIndex)); + + return KErrNone; + } + +TInt CFileCache::FlushDirty(CFsRequest* aOldRequest) + { + iLock.Wait(); + + CFsClientMessageRequest* newRequest = NULL; + + TInt r = DoFlushDirty(newRequest, aOldRequest, ETrue); + + iLock.Signal(); + + if (newRequest) + { + //To be used in notification framework. + //newRequest->iUID = aOldRequest->Message().Identity(); + newRequest->Dispatch(); + } + + return r; + } + + +void CFileCache::Purge(TBool aPurgeDirty) + { + iLock.Wait(); + + iCacheClient->Purge(aPurgeDirty); + + iLock.Signal(); + } + +void CFileCache::PropagateFlushErrorToAllFileShares() + { + FileShares->Lock(); + TInt count = FileShares->Count(); + while(count--) + { + CFileShare* share = (CFileShare*)(*FileShares)[count]; + if (&share->File() == iFileCB) + { + share->iFlushError = iFlushError; + } + } + FileShares->Unlock(); + } + +/** +CFileCache::DoFlushDirty() + +@param aFlushAll. If EFalse then only a single cacheline will be flushed + +returns CFsRequest::EReqActionBusy if a flush is in progress OR cacheline already in use + CFsRequest::EReqActionComplete if nothing to flush / flush completed + KErrNoMemory if failed to allocate a request + or one of the system wide error codes + + */ +TInt CFileCache::DoFlushDirty(CFsClientMessageRequest*& aNewRequest, CFsRequest* aOldRequest, TBool aFlushAll) + { + TInt64 pos; + TUint8* addr; + + if (iFlushBusy) + return CFsRequest::EReqActionBusy; + + TInt segmentCount = iCacheClient->FindFirstDirtySegment(pos, addr); + + if (segmentCount == 0) + { + TInt flushError = iFlushError; + iFlushError = KErrNone; + + // If this flush didn't originate from a client request return last error + if (!aOldRequest || !aOldRequest->Session()) + return (flushError == KErrNone)? CFsRequest::EReqActionComplete : flushError; + + // Return the last error from CFileShare::iFlushError + // and then clear CFileShare::iFlushError + + CFileShare* share = (CFileShare*) SessionObjectFromHandle( + aOldRequest->Message().Int3(), + FileShares->UniqueID(), + aOldRequest->Session()); + + TInt r = KErrNone; + if (share) + { + r = share->iFlushError; + share->iFlushError = KErrNone; + } + if (r == KErrNone) + return CFsRequest::EReqActionComplete; + + return r; + } + + + // NB aOldRequest->Session may be NULL - e.g for FileShareCloseOp + CSessionFs* session = aOldRequest && aOldRequest->Session() ? aOldRequest->Session() : iDirtyDataOwner; + + __ASSERT_ALWAYS(session, Fault(EFlushingWithSessionNull)); + + TInt r = AllocateRequest(aNewRequest, ETrue, session); + if (r != KErrNone) + return r; + + r = aNewRequest->PushOperation(0, 0, (TUint8*) NULL, 0); + + if (r != KErrNone) + { + aNewRequest->Free(); + aNewRequest = NULL; + return r; + } + + // set the number of segments to flush - either all dirty data or the equivalent of one full cacheline + aNewRequest->CurrentOperation().iScratchValue0 = + (TAny*) (aFlushAll?KMaxTInt:iCacheClient->CacheLineSizeInSegments()); + + + // issue flush request + r = FlushDirtySm(*aNewRequest); + + // We should only get three possible return codes from FlushDirtySm() : + // CFsRequest::EReqActionContinue - a write request (aNewRequest) needs to be dispatched + // CFsRequest::EReqActionComplete - completed already - this can happen if dirty data was beyond file end + // CFsRequest::EReqActionBusy - first dirty cacheline is already in use. + __ASSERT_DEBUG( r == CFsRequest::EReqActionContinue || r == CFsRequest::EReqActionComplete || r == CFsRequest::EReqActionBusy, Fault(EBadRetCode)); + if (r == CFsRequest::EReqActionContinue || r == CFsRequest::EReqActionBusy) + { + // requeue the caller's request (aOldRequest) if there is one + return CFsRequest::EReqActionBusy; + } + else // CFsRequest::EReqActionComplete + { + aNewRequest->PopOperation(); + aNewRequest->Free(); + aNewRequest = NULL; + return r; + } + + } + + +TInt TFsFileWriteDirty::PostInitialise(CFsRequest* aRequest) + { + return CFileCache::CompleteFlushDirty(aRequest); + } + +TInt CFileCache::CompleteFlushDirty(CFsRequest* aRequest) + { + CFsMessageRequest& msgRequest = *(CFsMessageRequest*) aRequest; + + CFileShare* share; + CFileCB* file; + GetFileFromScratch(aRequest, share, file); + CFileCache* fileCache = file->FileCache(); + + __ASSERT_DEBUG(fileCache != NULL, Fault(ENoFileCache)); + +#ifdef _DEBUG + TInt cancelling = (msgRequest.LastError() == KErrCancel)?(TBool)ETrue:(TBool)EFalse; +#endif + + fileCache->iLock.Wait(); + TInt r = fileCache->FlushDirtySm(msgRequest); + fileCache->iLock.Signal(); + +#ifdef _DEBUG + __ASSERT_DEBUG(!cancelling || msgRequest.LastError() == KErrCancel, Fault(ERequestUncancelled)); +#endif + + // if this request has been cancelled we mustn't dispatch it again - + // we still need to call state machine however so that any locked segments can be unlocked + if (msgRequest.LastError() == KErrCancel) + r = CFsRequest::EReqActionComplete; + + return r; + } + +TInt CFileCache::FlushDirtySm(CFsMessageRequest& aMsgRequest) + { + enum states + { + EStBegin=0, + EStWriteToDisk, + EStWriteToDiskComplete, + EStMarkAsClean, + EStEnd + }; + + TMsgOperation* currentOperation = &aMsgRequest.CurrentOperation(); + TInt& lastError = aMsgRequest.LastError(); + TInt64 pos = 0; + + for(;;) + { + + switch(currentOperation->iState) + { + case EStBegin: + iFlushBusy = ETrue; + + // fall into... + + case EStWriteToDisk: + { + currentOperation->iState = EStWriteToDisk; + + TUint8* addr; + TInt segmentCount = iCacheClient->FindFirstDirtySegment(pos, addr); + + // keep track of how many segments we've written + currentOperation->iScratchValue0 = (TAny*) ((TInt) currentOperation->iScratchValue0 - segmentCount); + + // if no more dirty segments of if a genuine error then finish + // NB if the request has been cancelled then we still need to proceed and mark all the + // segments as clean, otherwise they will never get freed ! + if (segmentCount == 0 || (lastError != KErrNone && lastError != KErrCancel)) + { + currentOperation->iState = EStEnd; + break; + } + + TInt len = segmentCount << SegmentSizeLog2(); + + if (pos < iSize64) + // Result of Min shall be of size TInt + // Hence to suppress warning + len = (TInt)(Min(iSize64 - pos, (TInt64)len)); + else + len = 0; + + + // if writing past end of file or this request has been cancelled, just mark as clean + if (len == 0 || lastError == KErrCancel) + { + currentOperation->iState = EStMarkAsClean; + break; + } + + __CACHE_PRINT3(_L("CACHEFILE: FlushDirty first dirty pos %d, addr %08X count %d"), I64LOW(pos), addr, segmentCount); + + TInt filledSegmentCount; + TInt lockError; + addr = iCacheClient->FindAndLockSegments(pos, segmentCount, filledSegmentCount, lockError, ETrue); + + // If cacheline is busy, we need to post request to back of drive queue + // To dispatch to drive thread and intercept before DoRequestL() we must call iPostInitialise(), + // so set the state back to CFsRequest::EReqStatePostInitialise + if (lockError == KErrInUse) + { + __CACHE_PRINT(_L("FlushDirtySm() - cacheline BUSY !")); + aMsgRequest.SetState(CFsRequest::EReqStatePostInitialise); + return CFsRequest::EReqActionBusy; + } + else if (lockError != KErrNone) + { + iFlushBusy = EFalse; + lastError = lockError; + return CFsRequest::EReqActionComplete; + } + + currentOperation->Set(pos, len, addr, 0, EStWriteToDiskComplete); + if (pos < iSize64) + { + TInt r = aMsgRequest.PushOperation( + pos, len, addr, 0, + CompleteFlushDirty, + EStWriteToDiskComplete); + if (r != KErrNone) + { + iCacheClient->UnlockSegments(pos); + iFlushBusy = EFalse; + lastError = r; + return CFsRequest::EReqActionComplete; + } + + return CFsRequest::EReqActionContinue; // continue on to TFsFileWrite::DoRequestL()() + } + } + + case EStWriteToDiskComplete: + { +#ifdef _DEBUG + // simulate a media eject to test critical error server + if (CCacheManagerFactory::CacheManager()->SimulateWriteFailureEnabled()) + { + lastError = KErrNotReady; + iDrive->Dismount(); + } +#endif + pos = currentOperation->iReadWriteArgs.iPos; + iCacheClient->UnlockSegments(pos); + } + // fall into... + + case EStMarkAsClean: + { + // NB pos must be set by EStWriteToDiskComplete or EStWriteToDisk + + if (lastError != KErrNone) + { + __CACHE_PRINT1(_L("CACHEFILE: WriteThrough FAILED %d"), lastError); + + lastError = HandleWriteDirtyError(lastError); + + // retry ? + if (lastError == KErrNone) + { + // clear error and try again + currentOperation->iState = EStWriteToDisk; + break; + } + + iFlushError = lastError; + PropagateFlushErrorToAllFileShares(); + + __CACHE_PRINT2(_L("CACHEFILE: Resetting size from %ld to %ld"), iSize64, iFileCB->Size64()); + SetSize64(iFileCB->Size64()); + } + + if (lastError != KErrNone) + { + // Destroy ALL cached data (dirty and non-dirty) ! + iCacheClient->Purge(ETrue); + } + else + { + // Mark segment as clean + iCacheClient->MarkSegmentsAsClean(pos); + } + + + if (TInt(currentOperation->iScratchValue0) > 0) + currentOperation->iState = EStWriteToDisk; + else + currentOperation->iState = EStEnd; + + + } + break; + + case EStEnd: + { + iFlushBusy = EFalse; + + TUint8* addr; + MarkFileClean(); + // Re-start dirty data timer if there is still some dirty data + if (iCacheClient->FindFirstDirtySegment(pos, addr) != 0) + FileDirty(aMsgRequest); + + return CFsRequest::EReqActionComplete; + } + } + } + } + +/** +Handle a dirty data write error + +Returns aError if dirty data should be thrown away or + KErrNone if write should be retried +*/ +TInt CFileCache::HandleWriteDirtyError(TInt aError) + { + __THRD_PRINT3(_L(">TRACE: CFileCache::HandleWriteDirtyError() aError %d mounted %d changed %d"), aError, iDrive->IsMounted(), iDrive->IsChanged()); + + // normally the disk change will have been detected by TDrive::CheckMount() but occasionally, + // the change will occur while writing - in which case we need to mimick what TDrive::CheckMount does, + // to make sure we are in a consisten state i.e. dismount the drive and set iCurrentMount to NULL + if (iDrive->IsChanged()) + { + iDrive->SetChanged(EFalse); + if (iDrive->IsMounted()) // Dismount the mount if it is still marked as mounted + iDrive->Dismount(); + } + + // if error didn't occur because of a media eject, do nothing + if (aError == KErrNotReady && !iDrive->IsMounted()) + { + + TLocaleMessage line1; + TLocaleMessage line2; + line1=EFileServer_PutTheCardBackLine1; + line2=EFileServer_PutTheCardBackLine2; + + //-- create Notifier + CAsyncNotifier* notifier = CAsyncNotifier::New(); + if( !notifier ) + { + return aError; + } + + notifier->SetMount(iMount); + + + // While this (drive) thread is calling the notifier server (& effectively suspended), + // the main thread may call TFsFileRead::PostInitialise() or TFsFileWrite::PostInitialise() + // which would cause dead-lock unless we release the lock here. We also need to release + // the lock before calling CDriveThread::CompleteReadWriteRequests(). + iLock.Signal(); + + FOREVER + { + TInt buttonVal; + + TInt ret = notifier->Notify( + TLocaleMessageText(line1), + TLocaleMessageText(line2), + TLocaleMessageText(EFileServer_Button1), + TLocaleMessageText(EFileServer_Button2), + buttonVal); + if (ret!=KErrNone) + break; + if (buttonVal!=1) + break; // Abort + + + // Wait up to 3 seconds for a disk change - this is because there is often a substantial + // (one/two second) delay between inserting a card & getting a notification that the card is ready; + // if we give up too soon and fire of the notifier again, it's not very user-friendly ! + const TTimeIntervalMicroSeconds32 KHalfSecond(500000); + const TInt KRetries = 6;; + for (TInt n=0; !iDrive->IsChanged() && nReMount(*iMount); + + TRACE1(UTF::EBorder, UTraceModuleFileSys::ECMountCBReMountRet, EF32TraceUidFileSys, remountSuccess); + if (!remountSuccess) + continue; + + + iMount->Drive().SetChanged(EFalse); + + aError = KErrNone; // Retry + break; + } + + delete notifier; + + + // Cancel hung state + FsThreadManager::SetDriveHung(DriveNumber(), EFalse); + + + // media had been removed and NOT replaced: so destroy ALL cached data + // (dirty and non-dirty) for all filecaches on this drive + if (aError != KErrNone) + { + CDriveThread* pT=NULL; + TInt r=FsThreadManager::GetDriveThread(DriveNumber(), &pT); + if(r==KErrNone) + { + pT->CompleteReadWriteRequests(); + iDrive->PurgeDirty(*iMount); + } + } + + iLock.Wait(); + } + + + return aError; + } + + +/** +Mark file as dirty +*/ +void CFileCache::FileDirty(CFsMessageRequest& aMsgRequest) + { + CSessionFs* session = aMsgRequest.Session(); + __ASSERT_ALWAYS(session, Fault(EDirtyDataOwnerNull)); + + // Remember the last session which caused the file to become dirty + // Always record whether any session has reserved access so the CheckDiskSpace() behaves correctly + if (iDirtyDataOwner == NULL || session->ReservedAccess(iDriveNum)) + iDirtyDataOwner = session; + + // start a timer after which file will be flushed + CDriveThread* driveThread=NULL; + TInt r = FsThreadManager::GetDriveThread(iDriveNum, &driveThread); + if(r == KErrNone && driveThread != NULL) + iDirtyTimer.Start(driveThread, iDirtyDataFlushTime); + } + +//---------------------------------------------------------------------------- +/** + Mark the file as clean and stop dirty data timer +*/ +void CFileCache::MarkFileClean() + { + iDirtyDataOwner = NULL; + + if (!iDriveThread) + return; + + iDirtyTimer.Stop(); + } + + + +//************************************ +// TFileCacheSettings +//************************************ + +RArray* TFileCacheSettings::iFileCacheSettings = NULL; + + + +const TInt KDriveCacheSettingsArrayGranularity = 4; + + +TFileCacheSettings::TFileCacheConfig::TFileCacheConfig(TInt aDrive) : + iDrive(aDrive), + iFileCacheReadAsync(KDefaultFileCacheReadAsync), + iFairSchedulingLen(KDefaultFairSchedulingLen << KByteToByteShift), + iCacheSize(KDefaultFileCacheSize << KByteToByteShift), + iMaxReadAheadLen(KDefaultFileCacheMaxReadAheadLen << KByteToByteShift), + iClosedFileKeepAliveTime(KDefaultClosedFileKeepAliveTime << KByteToByteShift), // convert milliSecs -> microSecs (approximately) + iDirtyDataFlushTime(KDefaultDirtyDataFlushTime << KByteToByteShift) // convert milliSecs -> microSecs (approximately) + { + iFlags = ConvertEnumToFlags(KDefaultFileCacheRead, KDefaultFileCacheReadAhead, KDefaultFileCacheWrite); + } + + +TFileCacheFlags TFileCacheSettings::TFileCacheConfig::ConvertEnumToFlags(const TInt aFileCacheRead, const TInt aFileCacheReadAhead, const TInt aFileCacheWrite) + { + TInt flags(0); + + // read caching + if (aFileCacheRead == EFileCacheFlagEnabled) + flags|= EFileCacheReadEnabled; + else if (aFileCacheRead == EFileCacheFlagOn) + flags|= EFileCacheReadEnabled | EFileCacheReadOn; + + // read ahead + if (aFileCacheReadAhead == EFileCacheFlagEnabled) + flags|= EFileCacheReadAheadEnabled; + else if (aFileCacheReadAhead == EFileCacheFlagOn) + flags|= EFileCacheReadAheadEnabled | EFileCacheReadAheadOn; + + // write caching + if (aFileCacheWrite == EFileCacheFlagEnabled) + flags|= EFileCacheWriteEnabled; + else if (aFileCacheWrite == EFileCacheFlagOn) + flags|= EFileCacheWriteEnabled | EFileCacheWriteOn; + + return TFileCacheFlags(flags); + } + + + +_LIT8(KLitSectionNameDrive,"Drive%C"); + +static const TPtrC8 KCacheFlagEnumStrings[]= + { + _S8("OFF"), + _S8("ENABLED"), + _S8("ON"), + _S8(""), // terminator + }; +const TInt KMaxEnumLen = 7; + +void TFileCacheSettings::ReadEnum(const TDesC8& aSection, const TDesC8& aProperty, TInt32& aEnumVal, const TPtrC8* aEnumStrings) + { + TBuf8 buf; + if (!F32Properties::GetString(aSection, aProperty, buf)) + return; + TInt n; + const TPtrC8* enumString; + for (enumString=aEnumStrings, n=0; enumString->Length()!= 0; enumString++, n++) + { + if (buf.LeftTPtr(enumString->Length()).MatchF(*enumString) == 0) + { + aEnumVal = n; + break; + } + } + } + +TInt TFileCacheSettings::ReadPropertiesFile(TInt aDriveNumber) + { + Init(); + + + if (!TGlobalFileCacheSettings::Enabled()) + return KErrNone; + + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDriveNumber, driveCacheSettings); + if (r != KErrNone) + return r; + + // restore default settings in case they've been changed by SetFlags() + driveCacheSettings->iFlags = TFileCacheConfig::ConvertEnumToFlags(KDefaultFileCacheRead, KDefaultFileCacheReadAhead, KDefaultFileCacheWrite); + + + // Get file cache configuration settings for this drive + // N.B. Size/length values are specified in Kilobytes, timer values in Milliseconds + TBuf8<8> sectionName; + sectionName.Format(KLitSectionNameDrive, 'A' + aDriveNumber); + + TInt32 val; + // Read FileCacheSize + if (F32Properties::GetInt(sectionName, _L8("FileCacheSize"), val)) + driveCacheSettings->iCacheSize = val << KByteToByteShift; + + // ensure read-ahead len is not greater than the cache size + driveCacheSettings->iMaxReadAheadLen = + Min(driveCacheSettings->iMaxReadAheadLen, driveCacheSettings->iCacheSize); + + // Read FileCacheReadAsync + TBool bVal; + if (F32Properties::GetBool(sectionName, _L8("FileCacheReadAsync"), bVal)) + driveCacheSettings->iFileCacheReadAsync = bVal; + + // Read FairSchedulingLen + if (F32Properties::GetInt(sectionName, _L8("FairSchedulingLen"), val)) + driveCacheSettings->iFairSchedulingLen = val << KByteToByteShift; + + // Read ClosedFileKeepAliveTime - convert miliSecs to microSecs (approximately) + if (F32Properties::GetInt(sectionName, _L8("ClosedFileKeepAliveTime"), val)) + driveCacheSettings->iClosedFileKeepAliveTime = val << KByteToByteShift; + + // Read DirtyDataFlushTime - convert miliSecs to microSecs (approximately) + if (F32Properties::GetInt(sectionName, _L8("DirtyDataFlushTime"), val)) + driveCacheSettings->iDirtyDataFlushTime = val << KByteToByteShift; + + // get read, read-ahead and write states + TInt32 readVal = KDefaultFileCacheRead; + TInt32 readAheadVal = KDefaultFileCacheReadAhead; + TInt32 writeVal = KDefaultFileCacheWrite; + + ReadEnum(sectionName, _L8("FileCacheRead"), readVal, &KCacheFlagEnumStrings[0]); + ReadEnum(sectionName, _L8("FileCacheReadAhead"), readAheadVal, &KCacheFlagEnumStrings[0]); + ReadEnum(sectionName, _L8("FileCacheWrite"), writeVal, &KCacheFlagEnumStrings[0]); + driveCacheSettings->iFlags = TFileCacheConfig::ConvertEnumToFlags(readVal, readAheadVal, writeVal); + + __CACHE_PRINT7(_L("ReadPropertiesFile() drive %C flags %08X CacheSize %d FileCacheReadAsync %d iFairSchedulingLen %d iClosedFileKeepAliveTime %d iDirtyDataFlushTime %d\n"), + aDriveNumber + 'A', + driveCacheSettings->iFlags, + driveCacheSettings->iCacheSize, + driveCacheSettings->iFileCacheReadAsync, + driveCacheSettings->iFairSchedulingLen, + driveCacheSettings->iClosedFileKeepAliveTime, + driveCacheSettings->iDirtyDataFlushTime); + + return KErrNone; + } + + +TInt TFileCacheSettings::GetFileCacheConfig(TInt aDrive, TFileCacheConfig*& aConfig) + { + Init(); + + aConfig = NULL; + + TFileCacheConfig driveCacheSettingsToMatch(aDrive); + + TInt index = iFileCacheSettings->FindInUnsignedKeyOrder(driveCacheSettingsToMatch); + if (index == KErrNotFound) + { + // create a new entry. + TInt r = iFileCacheSettings->InsertInUnsignedKeyOrder(driveCacheSettingsToMatch); + if (r != KErrNone) + return r; + index = iFileCacheSettings->FindInUnsignedKeyOrder(driveCacheSettingsToMatch); + __ASSERT_ALWAYS(index != KErrNotFound, Fault(ECacheSettingsNotFound)); + } + + aConfig = &(*iFileCacheSettings)[index]; + return KErrNone; + } + +void TFileCacheSettings::SetFlags(TInt aDrive, TFileCacheFlags aFlags) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + driveCacheSettings->iFlags = aFlags; + } + +TFileCacheFlags TFileCacheSettings::Flags(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iFlags; + } + +void TFileCacheSettings::Init() + { + if (iFileCacheSettings == NULL) + { + iFileCacheSettings = new RArray(KDriveCacheSettingsArrayGranularity, _FOFF(TFileCacheConfig, iDrive)); + __ASSERT_ALWAYS(iFileCacheSettings != NULL, Fault(ECacheSettingsInitFailed)); + } + } + + +TBool TFileCacheSettings::FileCacheReadAsync(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iFileCacheReadAsync; + } + +TInt TFileCacheSettings::FairSchedulingLen(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iFairSchedulingLen; + } + +TInt TFileCacheSettings::MaxReadAheadLen(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iMaxReadAheadLen; + } + +TInt TFileCacheSettings::CacheSize(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iCacheSize; + } + +TInt TFileCacheSettings::ClosedFileKeepAliveTime(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iClosedFileKeepAliveTime; + } + + +TInt TFileCacheSettings::DirtyDataFlushTime(TInt aDrive) + { + TFileCacheConfig* driveCacheSettings; + TInt r = GetFileCacheConfig(aDrive, driveCacheSettings); + __ASSERT_ALWAYS(r == KErrNone && driveCacheSettings != NULL, Fault(ECacheSettingGetFailed)); + + return driveCacheSettings->iDirtyDataFlushTime; + } + +//************************************ +// TClosedFileUtils +//************************************ + +CFsObjectCon* TClosedFileUtils::iClosedFiles = NULL; + +void TClosedFileUtils::InitL() + { + iClosedFiles = TheContainer->CreateL(); + if (iClosedFiles == NULL) + User::LeaveIfError(KErrNoMemory); + + } + + +TBool TClosedFileUtils::IsClosed(CFileCache* aFileCache) + { + return (aFileCache->Container() == iClosedFiles); + } + +TInt TClosedFileUtils::Count() + { + return iClosedFiles->Count(); + } + +CFileCache* TClosedFileUtils::At(TInt aIndex) + { + return (CFileCache*) (*iClosedFiles)[aIndex]; + } + + +// Add a closed file to closed file container +void TClosedFileUtils::AddL(CFileCache* aFileCache, TBool aLock) + { + if (aFileCache->iDriveThread) + { + iClosedFiles->AddL(aFileCache, aLock); + + // start a timer after which file will be removed from closed queue + CDriveThread* driveThread=NULL; + TInt r = FsThreadManager::GetDriveThread(aFileCache->iDriveNum, &driveThread); + if(r == KErrNone && driveThread != NULL) + aFileCache->iClosedTimer.Start(driveThread, aFileCache->iClosedFileKeepAliveTime); + } + } + + + +// Remove a closed file from closed file container so that it can be re-opened +void TClosedFileUtils::ReOpen(CFileCache* aFileCache, TBool aLock) + { + // get the drive thread in case it has changed since the file was last open + const TInt r = FsThreadManager::GetDriveThread(aFileCache->iDriveNum, &aFileCache->iDriveThread); + if ((r == KErrNone) && aFileCache->iDriveThread) + aFileCache->iClosedTimer.Stop(); + + iClosedFiles->Remove(aFileCache, aLock); + } + +// Remove all closed files from closed file container and close them for good +void TClosedFileUtils::Remove() + { + RemoveFiles(NULL, NULL); + } + +// Remove all closed files belonging to a particular TDrive from closed file container and close them for good +void TClosedFileUtils::Remove(TInt aDrvNumber) + { + RemoveFiles(&TClosedFileUtils::TestDrive, (TAny*) aDrvNumber); + } + +// Remove a closed file from closed file container and close it for good +void TClosedFileUtils::Remove(CFileCache* aFileCache) + { + RemoveFiles(&TClosedFileUtils::TestFile, (TAny*) aFileCache); + } + +void TClosedFileUtils::RemoveFiles(TTestFunc aTestFunc, TAny* aTestVal) + { + Lock(); + + TInt count = TClosedFileUtils::Count(); + while(count--) + { + CFileCache& file = *(CFileCache*)(*iClosedFiles)[count]; + if ((aTestFunc == NULL) || ((*aTestFunc)(file, aTestVal))) + { + iClosedFiles->Remove(&file, EFalse); + file.Close(); + } + } + + Unlock(); + } + + +TBool TClosedFileUtils::TestDrive(CFileCache& aFileCache, TAny* aVal) + { + TBool r = (aFileCache.iDriveNum == (TInt) aVal)?(TBool) ETrue: (TBool) EFalse; + if (r) + { + __CACHE_PRINT1(_L("CLOSEDFILES: TestDrive() closing %S\n"), &aFileCache.FileNameF() ); + } + return r; + } +TBool TClosedFileUtils::TestFile(CFileCache& aFileCache, TAny* aVal) + { + TBool r = (&aFileCache == ((CFileCache*) aVal))?(TBool)ETrue:(TBool)EFalse; + if (r) + { + __CACHE_PRINT1(_L("CLOSEDFILES: TestFile() closing %S\n"), &aFileCache.FileNameF() ); + } + return r; + } + + + +void TClosedFileUtils::Lock() + { + iClosedFiles->Lock(); + } + +void TClosedFileUtils::Unlock() + { + iClosedFiles->Unlock(); + } + +