diff -r a41df078684a -r 4122176ea935 userlibandfileserver/fileserver/sfat32/sl_mnt.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/userlibandfileserver/fileserver/sfat32/sl_mnt.cpp Mon Dec 21 16:14:42 2009 +0000 @@ -0,0 +1,4566 @@ +// Copyright (c) 1996-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: +// Common CFatMountCB code for both EFAT.FSY and EFAT32.fsy +// +// + +/** + @file + @internalTechnology +*/ + +#include "sl_std.h" +#include "sl_cache.h" +#include "sl_leafdir_cache.h" +#include "sl_dir_cache.h" +#include "sl_scandrv.h" +#include + +TShortName DoGenerateShortNameL(const TDesC& aLongName,TInt& aNum,TBool aUseTildeSelectively); + +static void MarkClusterVisited(RBitVector& aFatBitVec, TUint32 aCluster); +static TBool IsClusterVisited(const RBitVector& aFatBitVec, TUint32 aCluster); +static TInt NextUnvisitedCluster(const RBitVector& aFatBitVec, TUint32 aCluster); + +//----------------------------------------------------------------------------------------- + +TFatVolParam::TFatVolParam() + { + Mem::FillZ(this, sizeof(TFatVolParam)); + } + +/** + populate the object with the values from the boot sector. + @param aBootSector a reference to the valid boots sector +*/ +void TFatVolParam::Populate(const TFatBootSector& aBootSector) + { + ASSERT(aBootSector.IsValid()); + + iSectorsPerCluster = aBootSector.SectorsPerCluster(); + iSectorSizeLog2 = Log2(aBootSector.BytesPerSector()); + iClusterSizeLog2 = iSectorSizeLog2+Log2(iSectorsPerCluster); + iFirstFatSector = aBootSector.FirstFatSector(); + iNumberOfFats = aBootSector.NumberOfFats(); + iFatSizeInBytes = aBootSector.TotalFatSectors()*aBootSector.BytesPerSector(); + iTotalSectors = aBootSector.VolumeTotalSectorNumber(); + iRootClusterNum = aBootSector.RootClusterNum(); //-- will be 0 for FAT12/16 + + iRootDirectorySector = aBootSector.RootDirStartSector(); + iRootDirEnd = (iRootDirectorySector + aBootSector.RootDirSectors()) << SectorSizeLog2(); //-- doesn't matter for FAT32 + + //-- get main and backup FSInfo sectors position, these fields will be 0 for FAT12/16 + iFSInfoSectorNum = aBootSector.FSInfoSectorNum(); + iBkFSInfoSectorNum = (TUint16)(aBootSector.BkBootRecSector()+iFSInfoSectorNum); //-- Bk FSInfo sector must follow the Bk boot sector + } + +TBool TFatVolParam::operator==(const TFatVolParam& aRhs) const + { + ASSERT(&aRhs != this); + if(&aRhs == this) + return ETrue; //-- comparing with itself + + return (Mem::Compare((TUint8*)this, sizeof(TFatVolParam), (TUint8*)&aRhs, sizeof(TFatVolParam)) == 0); + } + + +//----------------------------------------------------------------------------------------- + + +CFatMountCB::CFatMountCB() + { + __PRINT2(_L("CFatMountCB::CFatMountCB() 0x%x, %S"), this, &KThisFsyName); + iFatType = EInvalid; + iState = ENotMounted; + DBG_STATEMENT(iCBRecFlag = 0); //-- debug flag only + } + +CFatMountCB::~CFatMountCB() + { + __PRINT1(_L("#-CFatMountCB::~CFatMountCB() 0x%x"), this); + + DoDismount(); + + delete iNotifier; + delete iFatTable; + delete iRawDisk; + delete iLeafDirCache; + + } + +//----------------------------------------------------------------------------------------- + +CFatMountCB* CFatMountCB::NewL() + { + CFatMountCB* pSelf = new(ELeave) CFatMountCB; + + CleanupStack::PushL(pSelf); + pSelf->ConstructL(); + CleanupStack::Pop(pSelf); + + return pSelf; + } + +// second-stage constructor +void CFatMountCB::ConstructL() +{ + //-- create Notifier + iNotifier = CAsyncNotifier::New(); + if( !iNotifier ) + { + Close(); + User::Leave(KErrNoMemory); + } + + iNotifier->SetMount(this); + } + +//------------------------------------------------------------------------------------------------------------------- + +/** +Implementation of CMountCB::FileSystemClusterSize(). Returns cluster size of this mount. +@return Cluster size value if successful; otherwise KErrNotReady if the mount is not ready. +@see CMountCB::FileSystemClusterSize() +*/ +TInt CFatMountCB::ClusterSize() const + { + if (ClusterSizeLog2()) + return (1 << ClusterSizeLog2()); + + return KErrNotReady; + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + @leave KErrAccessDenied if the mount is read-only +*/ +void CFatMountCB::CheckWritableL() const + { + if(ReadOnly()) + { + __PRINT(_L("CFatMountCB is RO!")); + User::Leave(KErrAccessDenied); + } + } + +/** + @leave KErrCorrupt if the mount is in inconsistent state i.e high-level file and directory operations can not be performed +*/ +void CFatMountCB::CheckStateConsistentL() const + { + if(!ConsistentState()) + { + __PRINT(_L("CFatMountCB state is inconsistent !")); + User::Leave(KErrCorrupt); + } + } + + +//------------------------------------------------------------------------------------------------------------------- +/** + Helper Method. Check if the parameters of the volume being remounted are the same as current ones. + @return ETrue if volume parameters remained same. +*/ +TBool CFatMountCB::CheckVolumeTheSame() +{ + //-- initialise local drive + TInt nRes =InitLocalDrive(); + if(nRes != KErrNone) + return EFalse; + + //-- read the boot sector or its backup copy if the main is damaged. It will aslo validate it. + TFatBootSector bootSector; + nRes = ReadBootSector(bootSector, iRamDrive); + if(nRes != KErrNone) + return EFalse; + + //-- 1. check volume Uid + if(iUniqueID != bootSector.UniqueID()) + return EFalse; + + //-- check volume parameters, they must remain the same + TFatVolParam volParam; + volParam.Populate(bootSector); + + if(!(volParam == iVolParam)) + return EFalse; + + + return ETrue; + +} +//------------------------------------------------------------------------------------------------------------------- + +/** + Helper Method. Check if the parameters of the volume being remounted are the same as current ones. + If they are, re-initialises the mount. +*/ +void CFatMountCB::DoReMountL() + { + + if(!CheckVolumeTheSame()) + User::Leave(KErrGeneral); + + //-- get drive capabilities + TLocalDriveCapsV2Buf capsBuf; + User::LeaveIfError(LocalDrive()->Caps(capsBuf)); + + //-- the volume is the same as it was on original MountL() + //-- we need to re-initialize for the case when the media was removed, FAT or directory structure changed on other device and the media returned back. + DoDismount(); + + SetState(EMounting); + + InitializeL(capsBuf(), ETrue); //-- forcedly disable FSInfo usage. This will lead to FAT free clusters re-counting. + + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Try remount this Fat volume. Checks if the volume parameters remained the same as on original MountL() call, and + if they are, re-initialises the mount. This includes resetting all caches. + ! Do not call this method from TFatDriveInterface methods, like citical and non-critical notifiers ! This can lead to the + recursive loops and undefined behaviour. + + @return KErrNone if the remount was OK + system-wide error code otherwise +*/ +TInt CFatMountCB::ReMount() + { + __PRINT2(_L("CFatMountCB::ReMount(), drv:%d, curr state:%d"), DriveNumber(), State()); + + const TFatMntState currState = State(); + + //-- analyse the mount state and find out if we can remount at all. + switch(currState) + { + case ENotMounted: + __PRINT(_L("CFatMountCB::ReMount() Invalid mount state!")); + + ASSERT(0); + return KErrGeneral; + + //-- correct state, proceed to remount + default: + break; + } + + //-- there are 2 options here: + //-- 1. normally initialised mount had been forcedly dismounted (it can optionally have objects opened on it) + //-- in this case the DoReMountL() will succeed and everything will be fine, the objects will be accessible afterwards + //-- 2. the mount hasn't been initialised at all (it does not have for example, FAT table created etc.) + //-- in this case we may need to fake the success. This can only happen on forced mount by CFormatCB + TInt nRes; + TRAP(nRes, DoReMountL()); + + if(nRes != KErrNone) + { + //-- note that the mount may be here left in inconsistent state (EMounting) + //-- if DoReMountL() fails. This is OK, because we can not make any valid read/write operations in such a state and + //-- the drive must be dismounted and mounted again. File Server's TDrive shall do this. + __PRINT1(_L("CFatMountCB::ReMount() failed! code:%d"), nRes); + + //-- If we are in the EInit_Forced state, it means that we are trying to remount the volume that has been formatted. + //-- scenario: On formatting, if we can't read a bootsector, new _empty_ object of the CFatMountCB is created and + //-- it is used for performing a format. If the format has finished, but RFormat isn't closed yet and we try to access the volume, + //-- we will get here, because all members of the constructed mount will be zeroes. + if(currState == EInit_Forced) + { + __PRINT(_L("CFatMountCB::ReMount() simulating normal remount!")); + SetState(currState); + return KErrNone; + } + + return nRes; + } + + __PRINT1(_L("CFatMountCB::ReMount() Completed drv:%d"), DriveNumber()); + SetState(EInit_R); + return nRes; + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Reset the last leaf dir or invalidate leaf dir cache if leaf dir cache is + instantiated. +*/ + +void CFatMountCB::InvalidateLeafDirCache() + { + if (iLeafDirCache) + { + iLeafDirCache->Reset(); + } + else + { + User::Free(iLastLeafDir); + iLastLeafDir=NULL; + } + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Delete mount's caches + Moves CFatMountCB into ENotMounted state immediately. +*/ +void CFatMountCB::DoDismount() + { + __PRINT1(_L("CFatMountCB::DoDismount() drv:%d"), DriveNumber()); + + //-- try to flush and destroy FAT cache + if (iFatTable) + { + if(!ConsistentState() || ReadOnly()) + {//-- the mount state is inconsistent, so the data can't be flushed. Ignore dirty cache either. + iFatTable->Dismount(ETrue); + } + else + {//-- Try to flush the FAT - if this fails there's not much we can do + TRAPD(r, iFatTable->FlushL()); + iFatTable->Dismount(r != KErrNone); //-- ignore dirty data if we failed to flush the cache + } + } + + //-- destroy leafdir name cache, this cache will be re-created while mounting or re-mounting + //-- see CFatMountCB::InitializeL() + delete iLeafDirCache; + iLeafDirCache = NULL; + + //-- destroy directory cache, this cache will be re-created while mounting or re-mounting + //-- see CFatMountCB::InitializeL() + delete iRawDisk; + iRawDisk = NULL; + + //-- Set mount state to "Dismounted". Which means that there might be no caches, but the mount is alive, + //-- i.e. iFatTable & iRawDisk are valid + SetState(EDismounted); + } + +//----------------------------------------------------------------------------------------- + +/** old implementation */ +void CFatMountCB::FinaliseMountL() + { + FinaliseMountL(RFs::EFinal_RW); + } + +//----------------------------------------------------------------------------------------- +/** + Dismount the CFatMountCB and the drive. + called from TDrive::Dismount(). +*/ +void CFatMountCB::Dismounted() + { + __PRINT1(_L("CFatMountCB::Dismounted() drv:%d"), DriveNumber()); + + //-- n.b. it is no safe to do a kind of filnalisatin work here that implies accessing the media. + //-- this method may be called after the media change occured from the TDrive::Dismount(). It means + //-- that if we try to write some data here, they could be written into a different medium, if it had been + //-- physically changed. + + const TFatMntState prevState = State(); + + DoDismount(); //-- it will change mount state to EDismounted + DismountedLocalDrive(); + + //-- check if the previous state was EInit_Forced, which means that this method was called + //-- on the mount that might not be alive (no valid iFatTable & iRawDisk). + //-- This can happen only during format operation on non-mounted previously volume. + //-- this EInit_Forced state must be processed separately, see ::Remount() + if(prevState == EInit_Forced) + SetState(EInit_Forced); + + } + + +//------------------------------------------------------------------------------------------------------------------- + +/** + Find out if the mount is finalised. + @param aFinalised on exit will be ETrue if the maunt is finalised, EFalse otherwise. + @return standard error codes. +*/ +TInt CFatMountCB::IsFinalised(TBool& aFinalised) + { + switch(State()) + { + case EFinalised: //-- already explicitly finalised + aFinalised = ETrue; + return KErrNone; + + case EInit_W: //-- the volume had been written + aFinalised = EFalse; + return KErrNone; + + default: //-- it depends on the state + break; + } + + //-- find out if the volume is _physically_ finalised. + //-- It can be in the state EInit_R, but finalised before mounting + if(!VolCleanFlagSupported()) + return KErrNotSupported; + + TInt nRes = KErrNone; + TRAP(nRes, aFinalised = VolumeCleanL()); + + return nRes; + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + @return ETrue if the mount is in consistent state i.e. normally mounted. + See TFatMntState enum for more detail. +*/ +TBool CFatMountCB::ConsistentState() const + { + return (iState==EInit_R) || (iState==EInit_W) || (iState == EFinalised); + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Open CFatMountCB for write. I.e. perform some actions on the first write attempt. + This is a callback from TFatDriveInterface. + @return System wide error code. +*/ +TInt CFatMountCB::OpenMountForWrite() + { + if(State() == EInit_W) + return KErrNone; //-- nothing to do, the mount is already opened for write + + __PRINT1(_L("#- CFatMountCB::OpenMountForWrite() drv:%d\n"),DriveNumber()); + + ASSERT(State() == EInit_R || State() == EFinalised); + + //-- Check possible recursion. This method must not be called recursively. SetVolumeCleanL() works through direct disc access and + //-- can not call TFatDriveInterface methods that call this method etc. + ASSERT(iCBRecFlag == 0); + DBG_STATEMENT(iCBRecFlag = 1); //-- set recursion check flag + + //-- do here some "opening" work, like marking volme as dirty + //-- be careful here, as soon as this is a callback from TFatDriveInterface, writing via TFatDriveInterface may cause some unwanted recursion. + + //-- mark the volume as dirty + TInt nRes=KErrNone; + TRAP(nRes, SetVolumeCleanL(EFalse)); + if(nRes == KErrNone) + { + SetState(EInit_W); + } + + DBG_STATEMENT(iCBRecFlag = 0); //-- reset recursion check flag + + return nRes; + + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Unfinalise the mount, reset "VolumeCleanShutDown" flag and change the state if necessarily. +*/ +void CFatMountCB::UnFinaliseMountL() + { + switch(State()) + { + case EFinalised: + case EInit_R: + SetVolumeCleanL(EFalse); //-- the mount, mark volume "dirty" + SetState(EInit_R); + return; + + case EInit_W: + return; //-- nothing to do + + default: + //-- other mount states are inconsistent; can't perform this operation + User::Leave(KErrAbort); + break; + + } + + } + +//------------------------------------------------------------------------------------------------------------------- + +/** + Finalise the mount. + + @param aOperation describes finalisation operation ,see RFs::TFinaliseDrvMode + @param aParam1 not used, for future expansion + @param aParam2 not used, for future expansion + + @leave System wide error code. particular cases: + KErrArgument invalid arguments + KErrInUse if the volume has opened objects (files, directories etc) + KErrCorrupt if the volume is corrupt + +*/ +void CFatMountCB::FinaliseMountL(TInt aOperation, TAny* /*aParam1*/, TAny* /*aParam2*/) + { + __PRINT2(_L("#- CFatMountCB::FinaliseMountL() op:%d, drv:%d"), aOperation, DriveNumber()); + + switch(aOperation) + { + case RFs::EFinal_RW: + case RFs::EFinal_RO: + break; + + case RFs::EForceUnfinalise: + UnFinaliseMountL(); + return; + + default: + __PRINT1(_L("#- CFatMountCB::FinaliseMountL() unexpected operation!:%d"), aOperation); + ASSERT(0); + User::Leave(KErrArgument); + return; + } + + //-- mount finalisation work + + ASSERT(aOperation == RFs::EFinal_RW || aOperation == RFs::EFinal_RO); + + if(State() == EFinalised) + {//-- the mount is already finalised. All we can do is to set it to RO mode + if(ReadOnly() && aOperation == RFs::EFinal_RW) + { + User::Leave(KErrAccessDenied); //-- can't override RO flag + } + + (void)LocalDrive()->Finalise(ETrue); + + if(aOperation == RFs::EFinal_RO) + { + SetReadOnly(ETrue); + return; + } + + return; + } + + if(LockStatus() != 0) + {//-- can't finalise the volume if it has opened objects and not in the consistent state. + //-- Theoretically, we can finalise the mount if we have files opened only for read, but at present, + //-- it's impossible to detect such situation. + User::Leave(KErrInUse); + } + + if(State() != EInit_R && State() != EInit_W) + {//-- can't finalise the mount because it can be in an inconsistent state; e.g. corrupt. + __PRINT1(_L("#- CFatMountCB::FinaliseMountL() Invalid mount State: %d"),State()); + User::Leave(KErrCorrupt); + } + + //-- flush FAT cache + FAT().FlushL(); + + //-- for FAT32 we may need to update data in FSInfo sectors + if(Is32BitFat()) + { + if(FAT().ConsistentState()) + {//-- FAT table state is consistent and the number of free clusters is known. + //-- Do it disregarding the mount state, it may help in the situation when 2 copies of the FSInfo are different for some reason. + DoUpdateFSInfoSectorsL(EFalse); + } + else + {//-- FAT table state is inconsistent, the most probable case here: background scan for free clusters is still working. + //-- in this case we can't put corect values into the FSInfo. + if(State() == EInit_W) + {//-- bad situation: free clusters may be being counted and someone has already written something on the volume at the same time. + //-- we do not know the exact number of free clustes and can't wait until scan finishes. Invalidate FSInfo. + __PRINT(_L("#- CFatMountCB::FinaliseMountL() invalidating FSInfo")); + DoUpdateFSInfoSectorsL(ETrue); + } + else + {//-- no changes on the volume, just do not update FSInfo + __PRINT(_L("#- CFatMountCB::FinaliseMountL() FAT state inconsistent; FSInfo isn't updated")); + } + + }//if(FAT().ConsistentState()) + + }//if(Is32BitFat()) + + + + //-- mark the volume as clean + SetVolumeCleanL(ETrue); + + //-- finally, put the volume into RO mode if required + if(aOperation == RFs::EFinal_RO) + SetReadOnly(ETrue); + + SetState(EFinalised); + } + + +//------------------------------------------------------------------------------------------------------------------- + +/** +@return ETrue if "VolumeClean" flag is supported i.e. this is not FAT12 +*/ +TBool CFatMountCB::VolCleanFlagSupported() const + { + const TFatType fatType=FatType(); + + ASSERT(fatType == EFat12 || fatType == EFat16 || fatType == EFat32); + return (fatType != EFat12); + } + +//----------------------------------------------------------------------------------------- + + +/** + Obtain the volume information. + All information except iSize and iFree has been added by TDrive::Volume(). + + @param aVolume on return will contain iSize & iFree fields filled with actual data. +*/ +void CFatMountCB::VolumeL(TVolumeInfo& aVolume) const + { + + //-- if true, this operation will be synchronous, i.e the client will be suspended until FAT32 scanning thread finishes, if running. + //-- the information if this operation is synchronous or not can be passed by client in TVolumeInfo::iFileCacheFlags field. + //-- if the client sets aVolume.iVolSizeAsync flag there, RFs::Volume() will be asynchronous, i.e the _current_ number of free clusters + //-- will be returned. + const TBool bSyncOp = !aVolume.iVolSizeAsync; + aVolume.iVolSizeAsync = EFalse; //-- reset this flag in order it not to be reused on the client side + + __PRINT2(_L("CFatMountCB::VolumeL() drv:%d, synch:%d"), DriveNumber(), bSyncOp); + const TDriveInfo& drvInfo=aVolume.iDrive; + +#if defined(__EPOC32__) + // if RAM drive, cap size according to HAL. + if (drvInfo.iType==EMediaRam) + { + TLocalDriveCapsV2Buf caps; + LocalDrive()->Caps(caps); + + const TInt max_drive_size=TInt(caps().iEraseBlockSize); + const TInt cur_drive_size=I64INT(caps().iSize); + + aVolume.iSize=max_drive_size; + aVolume.iFree=max_drive_size-cur_drive_size; + + aVolume.iSize=aVolume.iFree+iSize; + + TInt maxSize; + if (HAL::Get(HAL::EMaxRAMDriveSize, maxSize) == KErrNone) + { + // iSize will never grow beyond maxRam because of a check in medint. + // d <= f; (s{f} + f) - m <= f; s{f} <= m + __ASSERT_DEBUG(iSize <= maxSize, Fault(EFatRAMDriveSizeInvalid)); + if (aVolume.iSize > maxSize) + { + TInt64 d = aVolume.iSize - maxSize; + __ASSERT_DEBUG(d <= aVolume.iFree, Fault(EFatRAMDriveFreeInvalid)); + aVolume.iSize -= d; + aVolume.iFree -= d; + } + } + + aVolume.iSize-=ClusterBasePosition(); // Allow for bytes used by FAT etc + aVolume.iSize=(aVolume.iSize>>ClusterSizeLog2())<> ClusterSizeLog2()) << ClusterSizeLog2(); //-- round down to cluster size + + } + + +//----------------------------------------------------------------------------------------- + +// +// Set the volume label (write aVolume label into BPB & Volume Label File) +// aName string may be zero length but is assumed to contain no illegal characters or NULLs. +// +void CFatMountCB::SetVolumeL(TDes& aName) + { + + __PRINT(_L("CFatMountCB::SetVolumeL")); + + CheckStateConsistentL(); + CheckWritableL(); + + __ASSERT_ALWAYS(aName.Length()<=KVolumeLabelSize,User::Leave(KErrBadName)); + + TBuf8 buf8(KVolumeLabelSize); + buf8.Zero(); + LocaleUtils::ConvertFromUnicodeL(buf8, aName, TFatUtilityFunctions::EOverflowActionLeave); + aName.Zero(); + LocaleUtils::ConvertToUnicodeL(aName, buf8); // adjust aName (which may contain more underscores after this line than before) + + const TInt lengthOfBuf8=buf8.Length(); + // Pad to end with spaces if not empty. + if (lengthOfBuf8>0 && lengthOfBuf8=1); + + for(TInt i=0; iCacheCount() > 0) + { + iLeafDirCache->RemoveDirL(StartCluster(dosEntry)); + } + + EraseDirEntryL(dirEntryPos,dirEntry); + FAT().FreeClusterListL(StartCluster(dosEntry)); + FAT().FlushL(); + } + +//----------------------------------------------------------------------------------------- + +/** + Delete a file + @param aName file name + all trailing dots from the name will be removed +*/ +void CFatMountCB::DeleteL(const TDesC& aName) + { + __PRINT2(_L("CFatMountCB::DeleteL, drv:%d, %S"), DriveNumber(), &aName); + + CheckStateConsistentL(); + CheckWritableL(); + + TPtrC fullName = RemoveTrailingDots(aName); //-- remove trailing dots from the name + + TFatDirEntry firstEntry; + TEntryPos firstEntryPos(RootIndicator(),0); + FindEntryStartL(fullName,KEntryAttMaskSupported,firstEntry,firstEntryPos); + TEntryPos dosEntryPos=firstEntryPos; + TFatDirEntry dosEntry=firstEntry; + MoveToDosEntryL(dosEntryPos,dosEntry); + if ((dosEntry.Attributes()&KEntryAttReadOnly) || (dosEntry.Attributes()&KEntryAttDir)) + User::Leave(KErrAccessDenied); + // Can not delete a file if it is clamped + CMountCB* basePtr=(CMountCB*)this; + TInt startCluster=StartCluster(dosEntry); + if(basePtr->IsFileClamped(MAKE_TINT64(0,startCluster)) > 0) + User::Leave(KErrInUse); + EraseDirEntryL(firstEntryPos,firstEntry); + FAT().FreeClusterListL(StartCluster(dosEntry)); + FAT().FlushL(); + } + +//----------------------------------------------------------------------------------------- + +/** + + Rename or replace a directory entry. + Assumes all files are closed and replace is only passed files. + Assumes rename target does not exist or is the source file. + + --------------- operating mode -------------------------------------------- + + * rename mode + + aOldName exists | aNewName exists | result + N N leave KErrNotFound + N Y leave KErrNotFound + Y N rename aOldName -> aNewName + Y Y leave KErrAlreadyExists if(aOldName!=aNewName); otherwise do nothing + + * replace mode + + N N leave KErrNotFound + N Y leave KErrNotFound + Y N rename aOldName -> aNewName + Y Y contents and all file attributes of the "aNewName" are replaced with aOldName's. "aOldName" entries are deleted then. + + + @param aOldName entry name to be renamed or replaced + @param aNewName a new entry name + @param aMode specifies renaming / replacing + @param aNewDosEntryPos on exit contains new entry Pos. +*/ +void CFatMountCB::DoRenameOrReplaceL(const TDesC& aOldName, const TDesC& aNewName, TRenMode aMode, TEntryPos& aNewName_DosEntryPos) + { + __PRINT3(_L("CFatMountCB::DoRenameOrReplaceL() mode:%d old:%S, new:%S"), aMode, &aOldName, &aNewName); + + const TBool namesAreIdentical = FileNamesIdentical(aOldName, aNewName); //-- this is case-insensitive. + const TBool renameMode = (aMode == EModeRename); + const TBool replaceMode = !renameMode; + TInt nRes; + + if(namesAreIdentical && replaceMode) + return; //-- nothing to do, replacing file with itself + + //--------------------------------------------------------------------------------------------------------------------------- + //-- 1. find the entries of 'aOldName' file. It must always succeed, because FileServer firstly tries to locate 'aOldName' + + TFatDirEntry oldName_FirstEntry; //-- first entry of the "aOldName" entryset + TEntryPos oldName_FirstEntryPos(RootIndicator(), 0); //-- dir. pos of the start "aOldName" VFAT entry set + + FindEntryStartL(aOldName, KEntryAttMaskSupported, oldName_FirstEntry, oldName_FirstEntryPos); + + TFatDirEntry oldName_DosEntry = oldName_FirstEntry; //-- "aOldName" entry set DOS entry + TEntryPos oldName_DosEntryPos = oldName_FirstEntryPos;//-- dir. pos of the "aOldName" DOS entry + + MoveToDosEntryL(oldName_DosEntryPos, oldName_DosEntry); + + const TBool bOldNameIsVFAT = !(oldName_DosEntryPos == oldName_FirstEntryPos); //-- ETrue if "aOldName" is VFAT name, i.e. consists of mode than 1 entry + + //-- check if the file "aOldName" is clamped. In this case it can't be replaced. + if(replaceMode && (IsFileClamped(StartCluster(oldName_DosEntry)) > 0)) + User::Leave(KErrInUse); + + //--------------------------------------------------------------------------------------------------------------------------- + //-- 2. find the entry of 'aNewName' file. Further behavior depends on rename/replace mode and if this file exists or not + + //-- extract new file name from the full path + TPtrC ptrNewName; + TPtrC ptrNewNameParentDir; + const TInt delimPos = aNewName.LocateReverse(KPathDelimiter); + ptrNewName.Set(aNewName.Mid(delimPos+1)); + ptrNewNameParentDir.Set(aNewName.Left(delimPos+1)); + + //-- find the parent directory of the "aNewName" and create iterator for it + TLeafDirData leafDir; + const TEntryPos aNewName_ParentDirPos = TEntryPos(FindLeafDirL(ptrNewNameParentDir, leafDir), 0); //-- 'aNewName' parent directory iterator + aNewName_DosEntryPos = aNewName_ParentDirPos; + + TEntryPos newName_VFatEntryPos; //-- dir. pos of the start "aNewName" VFAT entry set + TFatDirEntry newName_DosEntry; + + TFileName fileName; + iFileCreationHelper.InitialiseL(ptrNewName); + TFatDirEntry startEntry; + + TRAP(nRes, DoFindL(ptrNewName, KEntryAttMaskSupported, + newName_VFatEntryPos, startEntry, aNewName_DosEntryPos, newName_DosEntry, + fileName, KErrNotFound, + &iFileCreationHelper, + leafDir)); + + if (nRes!=KErrNone && nRes!=KErrNotFound) + User::Leave(nRes); + + const TBool newFileExists = (nRes == KErrNone); //-- ETrue if 'aNewName' file exists. + const TBool bNewNameIsVFAT = !IsLegalDosName(ptrNewName, EFalse, EFalse, EFalse, EFalse, ETrue); + + if(renameMode && newFileExists) + { + if(!namesAreIdentical) + { + if ((newName_DosEntry.Attributes()&KEntryAttDir) != (oldName_DosEntry.Attributes()&KEntryAttDir)) + { + User::Leave(KErrAccessDenied); //-- leave with KErrAccessDenied if it is trying to rename a file + // to a dir or vice versa. + } + User::Leave(KErrAlreadyExists); //-- can't rename file if the file with 'aNewName' already exists + } + else + { + if(!bNewNameIsVFAT && !bOldNameIsVFAT) + return; //-- renaming DOS name to itself + } + //-- allow renaming entry to itself. "namesAreIdentical" is case-insensitive. use case: "FILE" -> "File" + } + + //--------------------------------------------------------------------------------------------------------------------------- + + if(replaceMode && newFileExists) + { + //--------------------------------------------------------------------------------------------------------------------------- + //-- replace contents of the 'aNewName' with 'aOldName' and remove 'aOldName' entries. + + //-- check if we are still trying to replace the file with itself, probably using short name alias + if(aNewName_DosEntryPos == oldName_DosEntryPos) + return; //-- nothing to do, it's the same file + + const TInt oldNameStartCluster = StartCluster(oldName_DosEntry); + const TInt newNameStartCluster = StartCluster(newName_DosEntry); //-- store starting cluster of the chain to be unlinked + + newName_DosEntry.SetStartCluster(oldNameStartCluster); + newName_DosEntry.SetSize(oldName_DosEntry.Size()); + newName_DosEntry.SetTime(oldName_DosEntry.Time(TTimeIntervalSeconds(0)), TTimeIntervalSeconds(0)); + newName_DosEntry.SetAttributes(oldName_DosEntry.Attributes()); + + if(IsRuggedFSys()) + { + //-- Note 1. + //-- set a special Id in reserved section for old and new entries. + //-- if write fails before the old entry gets erased, we will have 2 entries pointing to the same clusterchain. + //-- ScanDrive is responsible for fixing this situation by erasing entry with ID KReservedIdOldEntry. + //-- note that SetRuggedFatEntryId() uses "LastAccessTime" DOS FAT entry field to store the ID. + //-- in normal situation this field isn't used, though Windows checkdisk can chack its validiy. + //-- KReservedIdNewEntry == 0x0000 that corresponds to year 1980. + + newName_DosEntry.SetRuggedFatEntryId(KReservedIdNewEntry); + oldName_DosEntry.SetRuggedFatEntryId(KReservedIdOldEntry); + WriteDirEntryL(oldName_DosEntryPos, oldName_DosEntry); + } + + //-- write 'aNewName' DOS dir. entry data back + WriteDirEntryL(aNewName_DosEntryPos, newName_DosEntry); + + //-- erase "oldName" entryset. + EraseDirEntryL(oldName_FirstEntryPos, oldName_FirstEntry); + + //-- free 'aNewName' cluster list + FAT().FreeClusterListL(newNameStartCluster); + + if(IsRuggedFSys()) + FAT().FlushL(); + + } + else //if(replaceMode && newFileExists) + { + //--------------------------------------------------------------------------------------------------------------------------- + //-- Renaming 'aOldName' to 'aNewName': add 'aNewName' entry set and remove 'aOldName' entryset + + TFatDirEntry newDosEntry = oldName_DosEntry; + //-- generate short name for the 'aNewName' entryset and make new DOS entry + if(bNewNameIsVFAT) + {//-- need to generate a short name for VFAT entryset DOS entry + TShortName shortName; + + if (iFileCreationHelper.GetValidatedShortName(shortName) == KErrNotFound) + { + GenerateShortNameL(aNewName_DosEntryPos.Cluster(), ptrNewName, shortName, ETrue); + } + + newDosEntry.SetName(shortName); + } + else + {//-- just use 'aNewName' as DOS name. + TBuf8 tmp; //-- the name may be "XXXXXXXX.YYY" + tmp.Copy(ptrNewName); + newDosEntry.SetName(DosNameToStdFormat(tmp)); + } + + if(IsRuggedFSys()) + {//-- the the note(1) above + newDosEntry.SetRuggedFatEntryId(KReservedIdNewEntry); + oldName_DosEntry.SetRuggedFatEntryId(KReservedIdOldEntry); + WriteDirEntryL(oldName_DosEntryPos, oldName_DosEntry); + } + + //-- add new entryset to the directory + aNewName_DosEntryPos.iPos = 0; + aNewName_DosEntryPos.iCluster = aNewName_ParentDirPos.Cluster(); + + if (iFileCreationHelper.IsNewEntryPosFound()) + { + aNewName_DosEntryPos = iFileCreationHelper.EntryAddingPos(); + } + + if(bNewNameIsVFAT) + { + const TInt numEntries = NumberOfVFatEntries(ptrNewName.Length()); + AddDirEntryL(aNewName_DosEntryPos, numEntries); + WriteDirEntryL(aNewName_DosEntryPos, newDosEntry, ptrNewName); + } + else + {//-- new name is one DOS entry only + AddDirEntryL(aNewName_DosEntryPos, 1); + WriteDirEntryL(aNewName_DosEntryPos, newDosEntry); + } + + //-- erase old entryset. + EraseDirEntryL(oldName_FirstEntryPos, oldName_FirstEntry); + + //-- if we have renamed (moved) a directory, need to update its pointer to parent directory ('..' entry) + if((newDosEntry.Attributes() & KEntryAttDir)) + { + TEntryPos parentPtrEntPos(StartCluster(newDosEntry), 1*KSizeOfFatDirEntry); + + TFatDirEntry chFatEnt; + ReadDirEntryL(parentPtrEntPos, chFatEnt); + + const TUint parentDirStartCluster_Old = StartCluster(chFatEnt); + TUint parentDirStartCluster_New = aNewName_ParentDirPos.Cluster(); + + if(parentDirStartCluster_New == RootClusterNum() && parentDirStartCluster_New != 0) + {//-- we are in the root directory. for some reason, '..' entries of the directories in the root dir. + //-- must have starting cluster 0 + parentDirStartCluster_New = 0; + } + + if(parentDirStartCluster_Old != parentDirStartCluster_New) + { + chFatEnt.SetStartCluster(parentDirStartCluster_New); + WriteDirEntryL(parentPtrEntPos, chFatEnt); + } + // Invalidate leaf dir cache as it is hard to track the dir structure changes now + if (iLeafDirCache) + { + iLeafDirCache->Reset(); + } + } + }//else if(replaceMode && newFileExists) + + iFileCreationHelper.Close(); + } + +//----------------------------------------------------------------------------------------- + +/** + Rename 'aOldName' file/directory to 'aNewName' + all trailing dots from the names will be removed + + @param aOldName existing object name + @param aNewName new object name +*/ +void CFatMountCB::RenameL(const TDesC& aOldName, const TDesC& aNewName) + { + __PRINT3(_L("CFatMountCB::RenameL, drv:%d, %S %S"), DriveNumber(), &aOldName, &aNewName); + + CheckStateConsistentL(); + CheckWritableL(); + + TEntryPos newEntryPos; + DoRenameOrReplaceL(RemoveTrailingDots(aOldName), RemoveTrailingDots(aNewName) ,EModeRename, newEntryPos); + + if(!IsRuggedFSys()) + FAT().FlushL(); + } + +//----------------------------------------------------------------------------------------- + +/** + Replace contents of the 'aNewName' with the contents of 'aOldName' + all trailing dots from the names will be removed + + @param aOldName existing object name + @param aNewName new object name +*/ +void CFatMountCB::ReplaceL(const TDesC& aOldName,const TDesC& aNewName) + { + + __PRINT3(_L("CFatMountCB::ReplaceL, drv:%d, %S %S"), DriveNumber(), &aOldName, &aNewName); + + CheckStateConsistentL(); + CheckWritableL(); + + TEntryPos newEntryPos; + DoRenameOrReplaceL(RemoveTrailingDots(aOldName), RemoveTrailingDots(aNewName), EModeReplace, newEntryPos); + if(!IsRuggedFSys()) + FAT().FlushL(); + } + +//----------------------------------------------------------------------------------------- + + +/** + Try to find a directory entry by the given name and path. + This method _must_ leave if the entry is not found. See the caller. + + @param aName path to the directory object. all trailing dots from the name will be removed. + @param anEntry on return will contain the entry data + + @leave KErrPathNotFound if there is no path to the aName + KErrNotFound if the entry corresponding to the aName is not found + system-wide erorr code of media read failure. +*/ +void CFatMountCB::EntryL(const TDesC& aName,TEntry& anEntry) const + { + __PRINT2(_L("CFatMountCB::EntryL, drv:%d, %S"), DriveNumber(), &aName); + + CheckStateConsistentL(); + + TEntryPos entryPos(RootIndicator(),0); + TFatDirEntry entry; + TPtr fileName(anEntry.iName.Des()); + + TPtrC fullName = RemoveTrailingDots(aName); + TInt namePos=fullName.LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + TLeafDirData leafDir; + entryPos.iCluster=FindLeafDirL(fullName.Left(namePos), leafDir); + entryPos.iPos=0; + TEntryPos startPos; + TFatDirEntry startEntry; + + DoFindL(fullName.Mid(namePos),KEntryAttMaskSupported, + startPos,startEntry,entryPos,entry, + fileName,KErrNotFound, + NULL, + leafDir); + + + anEntry.iAtt=entry.Attributes(); + anEntry.iSize=entry.Size(); + anEntry.iModified=entry.Time(TimeOffset()); + + if (fileName.Length()==0) + { + TBuf8<0x20> dosName(DosNameFromStdFormat(entry.Name())); + LocaleUtils::ConvertToUnicodeL(fileName,dosName); + } + if ((TUint)anEntry.iSize>=sizeof(TCheckedUid)) + ReadUidL(StartCluster(entry),anEntry); + } + +//----------------------------------------------------------------------------------------- + +/** + Set directory entry details. + @param aName entry name; all trailing dots from the name will be removed + @param aTime entry modification time (and last access as well) + @param aSetAttMask entry attributes OR mask + @param aClearAttMask entry attributes AND mask + +*/ +void CFatMountCB::SetEntryL(const TDesC& aName,const TTime& aTime,TUint aSetAttMask,TUint aClearAttMask) + { + __PRINT2(_L("CFatMountCB::SetEntryL, drv:%d, %S"), DriveNumber(), &aName); + + CheckStateConsistentL(); + CheckWritableL(); + + TEntryPos firstEntryPos(RootIndicator(),0); + TFatDirEntry firstEntry; + FindEntryStartL(RemoveTrailingDots(aName),KEntryAttMaskSupported,firstEntry,firstEntryPos); + MoveToDosEntryL(firstEntryPos,firstEntry); + TUint setAttMask=aSetAttMask&KEntryAttMaskSupported; + if (setAttMask|aClearAttMask) + { + TInt att=firstEntry.Attributes(); + att|=setAttMask; + att&=(~aClearAttMask); + firstEntry.SetAttributes(att); + } + if (aSetAttMask&KEntryAttModified) + { + firstEntry.SetTime(aTime,TimeOffset()); + } + WriteDirEntryL(firstEntryPos,firstEntry); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::DoCheckFatForLoopsL(TInt aCluster, TInt& aPreviousCluster, TInt& aChangePreviousCluster, TInt& aCount) const +// +// Check one fat cluster for loops. +// + { + + if (aCluster==aPreviousCluster) + User::Leave(KErrCorrupt); // Found loop + + aCount++; + if (aCount==aChangePreviousCluster) + { + aCount=0; + aChangePreviousCluster<<=1; + aPreviousCluster=aCluster; + } + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::CheckFatForLoopsL(const TFatDirEntry& anEntry) const +// +// Check for loops +// + { + + TInt cluster=StartCluster(anEntry); + if (cluster==0 && anEntry.Size()==0) + return; + + TInt previousCluster=cluster; + TInt changePreviousCluster=1; + TInt count=0; + + + for(;;) + { + if ((TUint)cluster < KFatFirstSearchCluster || (!IsEndOfClusterCh(cluster) && (TUint)cluster>MaxClusterNumber())) + User::Leave(KErrCorrupt); + + if(!FAT().GetNextClusterL(cluster)) + break; + + DoCheckFatForLoopsL(cluster, previousCluster, changePreviousCluster, count); + } + + } + +//----------------------------------------------------------------------------------------- + +/** + Open/Create/Replace a file on the current mount. + + @param aName file name; all trailing dots from the name will be removed + @param aMode File open mode, See TFileMode + @param anOpen specifies action: open, create or replace the file + @param aFile pointer to the CFileCB object to populate + +*/ +void CFatMountCB::FileOpenL(const TDesC& aName,TUint aMode,TFileOpen anOpen,CFileCB* aFile) + { + __PRINT3(_L("CFatMountCB::FileOpenL, drv:%d, mode:%d, name:%S"), DriveNumber(), anOpen, &aName); + + CheckStateConsistentL(); + + TPtrC fullName = RemoveTrailingDots(aName); //-- remove trailing dots from the name + + TFatDirEntry firstEntry; + TEntryPos firstEntryPos(RootIndicator(),0); + TInt nPos=fullName.LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + TPtrC name(fullName.Mid(nPos)); + TInt ret = KErrNone; + + iFileCreationHelper.Close(); + if (anOpen == EFileCreate || anOpen == EFileReplace) + { + iFileCreationHelper.InitialiseL(name); + TRAP(ret,FindEntryStartL(fullName,KEntryAttMaskSupported,firstEntry,firstEntryPos,&iFileCreationHelper)); + } + else + { + TRAP(ret,FindEntryStartL(fullName,KEntryAttMaskSupported,firstEntry,firstEntryPos)); + } + + if (ret!=KErrNone && ret!=KErrNotFound) + User::Leave(ret); + + if (ret==KErrNone) + { + MoveToDosEntryL(firstEntryPos,firstEntry); + if ((firstEntry.Attributes()&KEntryAttDir) || (firstEntry.Attributes()&KEntryAttVolume)) + User::Leave(KErrAccessDenied); + if (anOpen==EFileCreate) + User::Leave(KErrAlreadyExists); + if ((firstEntry.Attributes()&KEntryAttReadOnly) && aMode&EFileWrite) + User::Leave(KErrAccessDenied); + if((aMode & EFileWrite) && (IsFileClamped(StartCluster(firstEntry))>0)) + User::Leave(KErrInUse); + CheckFatForLoopsL(firstEntry); + } + else + { + if (anOpen==EFileOpen) + User::Leave(KErrNotFound); + + //-- here we try to either create or replace file + CheckWritableL(); + + TLeafDirData leafDir; + + TInt numEntries = iFileCreationHelper.NumOfAddingEntries(); + TShortName shortName; + if (iFileCreationHelper.GetValidatedShortName(shortName) == KErrNotFound) + { + firstEntryPos.iCluster=FindLeafDirL(fullName.Left(nPos), leafDir); + GenerateShortNameL(firstEntryPos.iCluster,name,shortName,ETrue); + } + + if (iFileCreationHelper.IsNewEntryPosFound()) + { + firstEntryPos = iFileCreationHelper.EntryAddingPos(); + } + else + { + firstEntryPos.iCluster=FindLeafDirL(fullName.Left(nPos), leafDir); + firstEntryPos.iPos=0; + } + + AddDirEntryL(firstEntryPos,numEntries); + firstEntry.InitZ(); + firstEntry.SetName(shortName); + firstEntry.SetStartCluster(0); + + TTime now; + now.UniversalTime(); + firstEntry.SetCreateTime(now, TimeOffset() ); + + if (iFileCreationHelper.IsTrgNameLegalDosName()) + WriteDirEntryL(firstEntryPos,firstEntry); + else + WriteDirEntryL(firstEntryPos,firstEntry,name); + } + + CFatFileCB& file=(*((CFatFileCB*)aFile)); + file.SetL(firstEntry,(TShare)(aMode&KFileShareMask),firstEntryPos); + if (anOpen==EFileReplace && file.Size()) + { + file.SetSizeL(0); + file.SetSize(0); + } + if (file.IsSeekIndex()==EFalse) + file.CreateSeekIndex(); + if (anOpen==EFileReplace || anOpen==EFileCreate) + file.SetArchiveAttribute(); + + if(!IsRuggedFSys()) + FAT().FlushL(); + + iFileCreationHelper.Close(); + } + +//----------------------------------------------------------------------------------------- + + +/** + Open a directory on the current mount. + + @param aName path to the object in the directory we want to open; all trailing dots from the name will be removed + @param aDir dir. CB to be filled in. + + If there is no such a path, this method must leave with KErrPathNotFound + + @leave KErrPathNotFound if thereis no such path + @leave error code on media read fault +*/ +void CFatMountCB::DirOpenL(const TDesC& aName,CDirCB* aDir) + { + __PRINT2(_L("CFatMountCB::DirOpenL, drv:%d, %S"), DriveNumber(), &aName); + + CheckStateConsistentL(); + + const TPtrC dirName = RemoveTrailingDots(aName); //-- remove trailing dots from the name + + TInt namePos=dirName.LocateReverse(KPathDelimiter); + + TFatDirEntry dosEntry; + TEntryPos dosEntryPos(RootIndicator(),0); + if (namePos==0) + InitializeRootEntry(dosEntry); + else + { + TPtrC dirPath=dirName.Left(namePos); + TInt dirPos=dirPath.LocateReverse(KPathDelimiter)+1; + TLeafDirData leafDir; + dosEntryPos.iCluster=FindLeafDirL(dirPath.Left(dirPos), leafDir); // Find directory before leaf + dosEntryPos.iPos=0; + + TFileName fileName; + TEntryPos startPos; + TFatDirEntry startEntry; + DoFindL(dirPath.Mid(dirPos), + KEntryAttMatchMask|KEntryAttMatchExclusive, + startPos, startEntry, dosEntryPos, dosEntry, + fileName, KErrPathNotFound, + NULL, + leafDir); + + + } + + TPtrC matchName(dirName.Mid(namePos+1)); + if (matchName.Length()==0) + matchName.Set(_L("*")); + + ((CFatDirCB*)aDir)->SetDirL(dosEntry,matchName); + + } + +//----------------------------------------------------------------------------------------- + +TBool CFatMountCB::IsDirectoryEmptyL(TInt aCluster) +// +// Check aCluster contains no directory entries other than . and .. +// + { + + __PRINT(_L("CFatMountCB::IsDirectoryEmptyL")); + TEntryPos dirEntryPos(aCluster,0); + TFatDirEntry dirEntry; + FOREVER + { + ReadDirEntryL(dirEntryPos,dirEntry); + MoveToDosEntryL(dirEntryPos,dirEntry); + if (dirEntry.IsParentDirectory() || dirEntry.IsCurrentDirectory()) + goto LoopEnd; + if (dirEntry.IsEndOfDirectory()) + return ETrue; + if (IsRootDir(dirEntryPos)&&(dirEntryPos.iPos+StartOfRootDirInBytes()==RootDirEnd())) + return ETrue; // Root Directory has no end of directory marker + if (!dirEntry.IsErased()) + return EFalse; +LoopEnd: + MoveToNextEntryL(dirEntryPos); + } + } + +//----------------------------------------------------------------------------------------- + +/** + Overwrite as many contiguous file clusters as possible. +*/ +void CFatMountCB::DoWriteToClusterListL(TEntryPos& aPos,TInt aLength,const TAny* aSrc,const RMessagePtr2& aMessage,TInt anOffset, TInt aLastcluster, TInt &aBadcluster, TInt &aGoodcluster) + { + + __PRINT(_L("CFatMountCB::DoWriteToClusterListL")); + __ASSERT_ALWAYS(aPos.Cluster()>=KFatFirstSearchCluster,User::Leave(KErrCorrupt)); + + TInt endCluster=0; + + const TInt clusterRelativePos=ClusterRelativePos(aPos.iPos); + const TInt maxClusters=((aLength+clusterRelativePos-1)>>ClusterSizeLog2())+1; + const TInt clusterListLen=FAT().CountContiguousClustersL(aPos.iCluster,endCluster,maxClusters); + const TInt writeLength=Min(aLength,(clusterListLen<WriteL(dataStart,writeLength,aSrc,aMessage,anOffset)); + + if(r == KErrNone) // Write succeded + { + aPos.iPos+=writeLength; + aPos.iCluster=endCluster; + return; + } + + if(r != KErrCorrupt) // failure not due to corruption so propogate up + User::Leave(r); + + TErrorInfoBuf errinf; + r = iRawDisk->GetLastErrorInfo(errinf); + + if(r == KErrNone && errinf().iReasonCode == TErrorInfo::EBadSector) // GetLastErrorInfo succeded and Last Error was caused by bad sector + { + + const TInt badcluster = (TInt)(((dataStart + errinf().iErrorPos) - ClusterBasePosition())>>ClusterSizeLog2())+KFatFirstSearchCluster; + TInt goodcluster = FAT().AllocateSingleClusterL(badcluster); + + //Calculate cluster number to check whether this write started at the beginning of new cluster or middle of previous cluster. + TInt cluster = aPos.iCluster; + if ( (aPos.iPos) && ((aPos.iPos)==((aPos.iPos >> ClusterSizeLog2())<> ClusterSizeLog2()) << ClusterSizeLog2()); + dataStart = FAT().DataPositionInBytes(aPos.iCluster) + ClusterRelativePos((aPos.iPos - sizeToRead)); + + + //-- Allocate the buffer required to copy the contents from bad cluster + RBuf8 clustBuf; + CleanupClosePushL(clustBuf); + if(clustBuf.CreateMax(sizeToRead) != KErrNone) + { + FAT().FreeClusterListL(goodcluster); + User::Leave(KErrNoMemory); + } + + r = LocalDrive()->Read(dataStart, sizeToRead, clustBuf); //Read the contents into buffer + if(r != KErrNone) //If read fails dont do anything not even marking bad cluster. + { + FAT().FreeClusterListL(goodcluster); + User::Leave(r); + } + + //Copy the bad and good cluster,required to adjust the start cluster number. + if(aBadcluster == 0) + aBadcluster = badcluster; + + aGoodcluster = goodcluster; + + FOREVER + { + //Calculate and copy the contents to new cluster. + aPos.iCluster = goodcluster; + dataStart = FAT().DataPositionInBytes(aPos.iCluster) + ClusterRelativePos(aPos.iPos - sizeToRead); + + r = LocalDrive()->Write(dataStart, clustBuf); + if(r == KErrNone) + { // Copied contents to new cluster so fix up the chain and mark the cluster as bad. + FAT().WriteL(goodcluster, FAT().ReadL(badcluster)); + FAT().MarkAsBadClusterL(badcluster); + aGoodcluster = goodcluster; + CleanupStack::PopAndDestroy(&clustBuf); //-- deallocate a cluster buffer + return; + } + else if(r == KErrCorrupt) + { + r = LocalDrive()->GetLastErrorInfo(errinf); + if(r == KErrNone && errinf().iReasonCode == TErrorInfo::EBadSector) + { //Allocate new cluster and adjust the cluster list. + goodcluster = FAT().AllocateSingleClusterL(aPos.iCluster); + FAT().MarkAsBadClusterL(aPos.iCluster); + continue; + } + r = KErrCorrupt; + } + //Not able to write successfully so dont alter the original list. + aBadcluster = aGoodcluster = 0; + FAT().FreeClusterListL(goodcluster); + User::Leave(r); + } + + }//if((aPos.iPos != 0) && (badcluster == aPos.iCluster) && (aLastcluster == 0) && (aPos.iCluster == cluster)) + + if((badcluster == aPos.iCluster) && (aLastcluster == 0)) //bad cluster at beginning of original clusterlist + { + // return bad and good clusters for CFatFileCB to fix up + FAT().WriteL(goodcluster, FAT().ReadL(badcluster)); + aBadcluster = badcluster; + aGoodcluster = goodcluster; + aPos.iCluster = goodcluster; + } + else //fix up chain + { + FAT().WriteL(goodcluster, FAT().ReadL(badcluster)); + if(badcluster > aPos.iCluster) //bad cluster not first in this contiguous list + FAT().WriteL(badcluster-1, goodcluster); + else //first cluster of this contigous list bad so update last cluster of previous contiguous list + FAT().WriteL(aLastcluster, goodcluster); + } + + FAT().MarkAsBadClusterL(badcluster); + + + return; + } + User::Leave(KErrCorrupt); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::WriteToClusterListL(TEntryPos& aPos,TInt aLength,const TAny* aSrc,const RMessagePtr2& aMessage,TInt anOffset, TInt &aBadcluster, TInt& aGoodcluster) +// +// Overwrite cluster list. +// + { + + __PRINT(_L("CFatMountCB::WriteToClusterListL")); + __ASSERT_ALWAYS(aPos.Cluster()>=KFatFirstSearchCluster,User::Leave(KErrCorrupt)); + + const TUint startPos=aPos.iPos; + const TUint temp=startPos>>ClusterSizeLog2(); + const TUint length = (TUint)aLength; + + if ( (startPos) && ((startPos)==(temp<startPos,User::Leave(KErrCorrupt)); + previouscluster=aPos.iCluster; + if (offset=length) + return; + } + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::DoReadFromClusterListL(TEntryPos& aPos,TInt aLength,const TAny* aTrg,const RMessagePtr2& aMessage,TInt anOffset) const +// +// Read from as many contiguous file clusters as possible +// + { + + __PRINT(_L("CFatMountCB::DoReadFromClusterListL")); + + TInt endCluster=0; + + const TInt clusterRelativePos=ClusterRelativePos(aPos.iPos); + const TInt maxClusters=((aLength+clusterRelativePos-1)>>ClusterSizeLog2())+1; + const TInt clusterListLen=FAT().CountContiguousClustersL(aPos.iCluster,endCluster,maxClusters); + const TInt readLength=Min(aLength,(clusterListLen<ReadL(dataStart,readLength,aTrg,aMessage,anOffset)); + + if(r == KErrNone) // Read succeded + { + aPos.iPos+=readLength; + aPos.iCluster=endCluster; + return; + } + if(r != KErrCorrupt) // failure not due to corruption so propogate up + User::Leave(r); + + TErrorInfoBuf errinf; + r = iRawDisk->GetLastErrorInfo(errinf); + + if(r == KErrNone && errinf().iReasonCode == TErrorInfo::EBadSector) // GetLastErrorInfo succeded and Last Error was caused by bad sector + { + TInt badcluster = (TInt)(((dataStart + errinf().iErrorPos) - ClusterBasePosition())>>ClusterSizeLog2())+KFatFirstSearchCluster; + FAT().MarkAsBadClusterL(badcluster); + } + + User::Leave(KErrCorrupt); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::ReadFromClusterListL(TEntryPos& aPos,TInt aLength,const TAny* aTrg,const RMessagePtr2& aMessage,TInt anOffset) const +// +// Read from cluster list +// + { + + __PRINT(_L("CFatMountCB::ReadFromClusterListL")); + __ASSERT_ALWAYS(aPos.Cluster()>=KFatFirstSearchCluster,User::Leave(KErrCorrupt)); + + const TInt startPos=aPos.iPos; + const TInt temp=startPos>>ClusterSizeLog2(); + + if ( (startPos) && ((startPos)==(temp<=aLength) + return; + } + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::FindLeafDirL(const TDesC& aName, TLeafDirData& aLeafDir) const +// +// Navigate the path to find the leaf directory. +// Returns the startcluster of data for the directory found. +// + { + + __PRINT(_L("CFatMountCB::FindLeafDirL")); + + TLex lex(aName); + TInt r; + TEntryPos entryPos(RootIndicator(),0); + + if (iLeafDirCache == NULL) + { + TInt leaflen=(iLastLeafDir) ? iLastLeafDir->Length() : 0; + TInt namelen=aName.Length(); + if (leaflen>1 && namelen>=leaflen && *iLastLeafDir==aName.Left(leaflen)) + { + if (leaflen==namelen) + return(iLastLeafDirCluster); + lex.Inc(leaflen-1); + entryPos.iCluster=iLastLeafDirCluster; + } + } + else + { + // Skip root directory + if (iLeafDirCache->CacheCount() > 0 && aName.Length() > 1) + { + TInt err = iLeafDirCache->FindInCache(aName, aLeafDir); + if (err == KErrNone) + { + ASSERT(aLeafDir.iClusterNum > 0); + return aLeafDir.iClusterNum; + } + else if (err != KErrNotFound) + { + User::LeaveIfError(err); + } + } + } + + FOREVER + { + lex.Inc(); // Skip path delimiter + lex.Mark(); + r=lex.Remainder().Locate(KPathDelimiter); + if (r==KErrNotFound) + r=lex.Remainder().Length(); + if (r==0) // End of the path + break; + lex.Inc(r); // Set the token length + TFatDirEntry entry; + + TFileName fileName; + TEntryPos startPos; + TFatDirEntry startEntry; + DoFindL(lex.MarkedToken(), + KEntryAttMatchMask|KEntryAttMatchExclusive, + startPos, startEntry, entryPos, entry, + fileName, KErrPathNotFound, + NULL, + aLeafDir); + + + entryPos.iCluster=StartCluster(entry); + entryPos.iPos=0; + } + + if (iLeafDirCache == NULL) + { + AllocBufferL(((CFatMountCB*)this)->iLastLeafDir,aName); + ((CFatMountCB*)this)->iLastLeafDirCluster=entryPos.iCluster; + } + else + { + if (aName.Length() > 1) + { + aLeafDir = TLeafDirData(entryPos.iCluster); + iLeafDirCache->AddToCacheL(aName, aLeafDir); + } + } + + return entryPos.iCluster; + } + +//----------------------------------------------------------------------------------------- + +/** + Search for a specified name winthin directory cache + Works similary to TBool CFatMountCB::DoFindL() + + @param anAtt attributes of the object to find + @param aStartEntryPos on return in case of VFAT entry will contain start position of the VFAT dir. entry + @param aStartEntry on return will contain first VFAT dir entry + @param aDosEntryPos the search will start from this position of dir entry, on return it will contain result DOS entry position, last one for VFAT case + @param aDosEntry on return will contain DOS dir entry (the last one for VFAT case) + @param aFileName in the case of VFAT entry and on success here will be returned a long filename + @param aAuxParam some parameters package + @param aFileCreationHelper a helper package for file creations + + @return ETrue if the specified name is found in the cache. In this case aStartEntryPos, aStartEntry, aDosEntryPos, aDosEntry, aFileName will contain valid values +*/ +TBool CFatMountCB::DoRummageDirCacheL(const TUint anAtt, TEntryPos& aStartEntryPos, + TFatDirEntry& aStartEntry, TEntryPos& aDosEntryPos, + TFatDirEntry& aDosEntry, TDes& aFileName, + const TFindHelper& aAuxParam, + XFileCreationHelper* aFileCreationHelper, + const TLeafDirData& aLeafDir) const + { + TBool bCacheMatchFound = EFalse; + + //-- get an interface to the Dir. cache + MWTCacheInterface* pDirCache = iRawDisk->DirCacheInterface(); + ASSERT(pDirCache); + if(!pDirCache) + return EFalse; + + //-- save original values in order to restore them in the case of negative search results + TEntryPos StartEntryPos1(aStartEntryPos); + TEntryPos DosEntryPos1(aDosEntryPos); + TFatDirEntry StartEntry1(aStartEntry); + TFatDirEntry DosEntry1(aDosEntry); + + TInt64 nCachedLinPos; + + const TUint32 clSize = 1 << ClusterSizeLog2(); //-- media cluster size + const TUint32 cacheSz = pDirCache->CacheSizeInBytes(); //-- cache size in bytes + const TUint32 maxDirEntries = cacheSz >> KSizeOfFatDirEntryLog2; //-- maximal number of dir entries that can be in the cache + + const TUint pageSzLog2 = pDirCache->PageSizeInBytesLog2(); + TBool ScanMRUPageFirst = EFalse; + TBool MRUPageScanned = EFalse; + + // if MRU pos is availale, start with MRU page + if (aLeafDir.iMRUPos.Cluster()) + { + ScanMRUPageFirst = ETrue; + DosEntryPos1 = aLeafDir.iMRUPos; + } + + TInt numFound = 0; + TEntryPos startPos = DosEntryPos1; + TInt clusterNum = DosEntryPos1.iCluster; + + for(TUint32 entryCnt=0; entryCnt < maxDirEntries; ++entryCnt) + {//-- walk through directory cluster list. The loop is limited by maximal number of dir entries + //-- that can be cached. Helps to avoid problems with infinite (looped) directories + + if (IsEndOfClusterCh(DosEntryPos1.iCluster)) + { + // refer back to the last stored cluster position + // note aFileCreationHelper may not be initialised for file opening operations + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && clusterNum != DosEntryPos1.iCluster) + { + TEntryPos dummyPos(clusterNum, clSize - KSizeOfFatDirEntry); + aFileCreationHelper->SetEntryAddingPos(dummyPos); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + + if (ScanMRUPageFirst && !MRUPageScanned) + { + DosEntryPos1 = aDosEntryPos; + MRUPageScanned = ETrue; + continue; + } + break; //-- this was the last cluster in this directory + } + + const TUint32 pageStartPos = CalculatePageOffsetInCluster(DosEntryPos1.iPos, pageSzLog2); + DosEntryPos1.iPos = pageStartPos; + TBool PassedPageBoundary = EFalse; + + const TInt64 entryLinPos = MakeLinAddrL(DosEntryPos1); //-- linear media position of the cluster for this directory + const TUint32 cachePageSz = pDirCache->PosCached(entryLinPos, nCachedLinPos); //-- indicates if entryLinPos is cached + if(cachePageSz) + {//-- current page is in the directory cache + //__PRINT2(_L("#-!! CFatMountCB::DoRummageDirCacheL() Searching cl:%d, lin Pos:%X"),DosEntryPos1.iCluster,(TUint32)entryLinPos); + + //-- search to the end of the cached page. + // Note GetDirEntry() will read data beyond cache page boundary + const TUint32 nEntries = (1 << pageSzLog2) >> KSizeOfFatDirEntryLog2; + + TInt nErr; + //-- extract dir entries from the cached page and see if they match given name (aName) + /// until it reaches the next page + for(;;) + { + StartEntryPos1 = DosEntryPos1; + TInt clSave = DosEntryPos1.iCluster; //-- need to save current cluster number because GetDirEntry() & MoveToNextEntryL() can change it + + //-- get directory entry from the cache. We know that the DosEntryPos1 is cached. + nErr = GetDirEntry(DosEntryPos1, DosEntry1, StartEntry1, aFileName); + if(nErr != KErrNone) + break; + + if(DosEntry1.IsEndOfDirectory()) + { + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && !aFileCreationHelper->IsNewEntryPosFound()) + { + // note it is impossible to be at the end of the cluster chain here. + aFileCreationHelper->SetEntryAddingPos(DosEntryPos1); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + + if (ScanMRUPageFirst && !MRUPageScanned) + { + break; + } + + // if (!ScanMRUPageFirst || ScanMRUPageFirst && MRUPageScanned) + goto Exit; //-- this was the last entry in this directory, no reason to look further + } + + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && !aFileCreationHelper->IsNewEntryPosFound()) + { + if (!DosEntry1.IsErased() && !DosEntry1.IsGarbage()) + { + numFound = 0; + } + else + { + if (numFound == 0) + { + startPos = DosEntryPos1; + } + numFound++; + if (numFound == aFileCreationHelper->NumOfAddingEntries()) + { + aFileCreationHelper->SetEntryAddingPos(startPos); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + } + } + if(MatchEntryAtt(DosEntry1.Attributes(),anAtt)) + {//-- FAT or VFAT dir entry is extracted and attributes match. Compare names then. + + if(StartEntry1.IsVFatEntry()) + {//-- extracted entry is VFAT one, name can be in UNICODE + + // we only check short name candidates for long file names with VFAT entries, + // if it is a valid dos name, it will be checked by default + // note here target name is always fully specified + if (aFileCreationHelper && aFileCreationHelper->IsInitialised()) + { + aFileCreationHelper->CheckShortNameCandidates(DosEntry1.Name().Ptr()); + } + + TPtrC ptrAssembledName = RemoveTrailingDots(aFileName); + + if(ptrAssembledName.MatchF(aAuxParam.iTargetName) != KErrNotFound) + {//-- found match in cache + bCacheMatchFound = ETrue; + goto Exit; + } + else if(aAuxParam.TrgtNameIsLegalDos()) + { + if(aAuxParam.MatchDosEntryName(DosEntry1.Name().Ptr())) + { + bCacheMatchFound = ETrue; + goto Exit; + } + } + }//if(StartEntry1.IsVFatEntry()) + else if(aAuxParam.TrgtNameIsLegalDos()) + {//-- this is an old DOS FAT entry + + if(aAuxParam.MatchDosEntryName(DosEntry1.Name().Ptr())) + { + //-- Here is the trick that helps with the situation when VFAT entry is split into 2 halves + //-- between 2 clusters (or/and cache pages). I.e. 1st part of this long entry belongs to one cluster and even more might not be cached, + //-- While the rest of the entry, DOS part of it is the 1st entry in the cluster and cached. + //-- In this case if we search for short file name, we find it, but the aStartEntryPos will be incorrect, which leads to the directory corruption. + //-- The simple and quick solution - discard 1st DOS entry and return to old search. It shall be quite rare. + if(StartEntryPos1.iPos == 0) + {//-- this is the 1st FAT entry in the cluster. Discard it, see comments above. + __PRINT(_L("#------ CFatMountCB::DoRummageDirCacheL() discarding FAT Entry!!")); + goto Exit; + } + + bCacheMatchFound = ETrue; + goto Exit; + } + } + + }//if(bGotEntry && MatchEntryAtt(DosEntry1.Attributes(),anAtt)) + + // check boundaries after GetDirEntry() + // if we have cross the cluster boundary, break the for loop + if(DosEntryPos1.iCluster != clSave) + {//-- GetDirEntry() has decided to move to the next cluster. + DosEntryPos1.iCluster = clSave; + break; + } + + // if we are still in the same cluster, check the page boundary by + /// exam how many entries we have scanned within the cluster + const TUint entriesLooked = ((DosEntryPos1.iPos + KSizeOfFatDirEntry)- pageStartPos) >> KSizeOfFatDirEntryLog2; + if(entriesLooked > nEntries) + { + PassedPageBoundary = ETrue; + break; + } + + + // move to next entry before scanning next file + TRAP(nErr,MoveToNextEntryL(DosEntryPos1)); + if(nErr != KErrNone) + goto Exit; + + // check boundaries after MoveToNextEntryL() + if(DosEntryPos1.iCluster != clSave) + { + DosEntryPos1.iCluster = clSave; + break; + } + + if (entriesLooked + 1 > nEntries) + { + PassedPageBoundary = ETrue; + break; + } + + } //for(;;) + + } //if(iRawDisk->PosCached(...)) + + // scanning did not happen because the page is not cached, + // or + // scanning finished in last page and file is not found + + // if MRU page is not cached or + // we scan MRU page first and it is not scanned yet, then this must be the MRU page, + // we now start to scan from the beginning + if (ScanMRUPageFirst && !MRUPageScanned) + { + MRUPageScanned = ETrue; + DosEntryPos1 = aDosEntryPos; + DosEntryPos1.iPos = 0; + continue; + } + + // if we just finished scanning a page and still in the same cluster, then we crossed page + // boundary, continue with next page. + // note: although we are in the 'next page' already, this page might not be cached, so we need to + // check it via pDirCache->PosCached(entryLinPos, nCachedLinPos) and scan it properly. + if (PassedPageBoundary) + { + DosEntryPos1.iPos = CalculatePageOffsetInCluster(DosEntryPos1.iPos, pageSzLog2); + PassedPageBoundary = EFalse; + continue; + } + + //-- try to move to the next cluster of the directory file + + if(DosEntryPos1.Cluster() < KFatFirstSearchCluster) //-- small trick to get rid of TRAPping GetNextClusterL() + break; + + // record previous cluster number before move on + clusterNum = DosEntryPos1.iCluster; + + if(! FAT().GetNextClusterL(DosEntryPos1.iCluster)) + break; + + + } //for(TUint32 entryCnt=0; entryCnt< maxDirEntries; ++entryCnt) + + //--------------------------------- + Exit: + + if(bCacheMatchFound) + { + //-- if the position of the found in cache object is less than given, pretend that we haven't found anything + //-- Return to the old search, because it can be the case of the end of directory, which is quite difficult to + //-- detect in this situation. Note that the old part of DoFindL() leaves when the search reaches the end of dir. + TBool bFallBack=EFalse; + + if(DosEntryPos1.iCluster == aDosEntryPos.iCluster) + { + if(DosEntryPos1.iPos < aDosEntryPos.iPos) + bFallBack = ETrue; + } + else + { + if(MakeLinAddrL(DosEntryPos1) < MakeLinAddrL(aDosEntryPos)) + bFallBack = ETrue; + } + + if(bFallBack) + { + return EFalse; + } + + //-- Update parameters with new values + aStartEntryPos= StartEntryPos1; + aDosEntryPos = DosEntryPos1; + aStartEntry = StartEntry1; + aDosEntry = DosEntry1; + + const TInt64 mruPos = MakeLinAddrL(aDosEntryPos); + + pDirCache->MakePageMRU(mruPos); + + // only update the leaf dir cache when the original cache index is provided + if (aLeafDir.iClusterNum) + { + iLeafDirCache->UpdateMRUPos(TLeafDirData(aLeafDir.iClusterNum, aStartEntryPos)); + } + } + return bCacheMatchFound; + } + +//----------------------------------------------------------------------------------------- + +/** + initialise find helper with the target file name. + This is a quite expensive operation and initialisation is done only once. After this we know if the name is a legal dos one + and also have the corresponding generated DOS name for it. + + @param aTargetName target file name we are looking for in ::DoFindL() +*/ +void CFatMountCB::TFindHelper::InitialiseL(const TDesC& aTargetName) + { + if(isInitialised) + return; + + TInt count = 1; + + iTargetName.Set(aTargetName); + isLegalDosName = IsLegalDosName(aTargetName, ETrue, EFalse, EFalse, ETrue, EFalse); + + if(isLegalDosName) + {//-- iShortName will contain generated short DOS name by long filename + iShortName = DoGenerateShortNameL(aTargetName, count, ETrue); + } + + isInitialised = ETrue; + } + +/** + Perform binary comparison between a given the DOS entry name and the DOS name we generated in TFindHelper::Initialise(). + @param apDosEntryName pointer to the DOS entry name in XXXXXXXXYYY format + @return ETrue if the apDosEntryName is the same as generated iShortName +*/ +TBool CFatMountCB::TFindHelper::MatchDosEntryName(const TUint8* apDosEntryName) const + { + ASSERT(isInitialised); + + if(!isLegalDosName) + return EFalse; + + return (Mem::Compare(iShortName.Ptr(), KFatDirNameSize, apDosEntryName, KFatDirNameSize) == 0); + } + +//----------------------------------------------------------------------------------------- +const TInt KShortNameCandidatesNum = 4; +/** +Constructor of XFileCreationHelper class +*/ +CFatMountCB::XFileCreationHelper::XFileCreationHelper() + { + isInitialised = EFalse; + } + +/** +Destructor of XFileCreationHelper class +*/ +CFatMountCB::XFileCreationHelper::~XFileCreationHelper() + { + Close(); + } + +/** +Initialises a TFileCreationHelper object, generate a short name candidate pool. + +@param aTargetName Target file name for the potential new file. +@post TFileCreationHelper is fully initialised. +*/ +void CFatMountCB::XFileCreationHelper::InitialiseL(const TDesC& aTargetName) + { + // close before use, to avoid memory leak + Close(); + + iTargetName.Set(aTargetName); + // generates short name candidate(s) + TInt count = 1; + while (count <= KShortNameCandidatesNum) + { + TShortName shortNameCandidate = DoGenerateShortNameL(aTargetName, count, ETrue); + TInt err = iShortNameCandidates.Append(shortNameCandidate); + User::LeaveIfError(err); + + if (count == -1) // No tilde and number is needed + { + break; + } + else + count++; + } + + // calculate number of new entries needed + iNumOfAddingEntries = 1; + isTrgNameLegalDosName = IsLegalDosName(aTargetName, EFalse, EFalse, EFalse, EFalse, ETrue); + if (!isTrgNameLegalDosName) + iNumOfAddingEntries = (TUint16) NumberOfVFatEntries(iTargetName.Length()); + + isNewEntryPosFound = EFalse; + isInitialised = ETrue; + } + +/** +Close function of XFileCreationHelper class +*/ +void CFatMountCB::XFileCreationHelper::Close() + { + iShortNameCandidates.Close(); + isInitialised = EFalse; + } + +/** +Validates short name candidates. If the input dos entry name is found in the short name + candidate pool, the corresponding short name candidate will be removed from the pool. + +@param apDosEntryName An existing short name, to compare with the candidates. +@pre Object should be initialised +*/ +void CFatMountCB::XFileCreationHelper::CheckShortNameCandidates(const TUint8* apDosEntryName) + { + ASSERT(isInitialised); + if (!isInitialised) + return; + + if (iShortNameCandidates.Count() > 0) + { + for (TInt i = 0; i < iShortNameCandidates.Count(); i++) + { + if (Mem::Compare(iShortNameCandidates[i].Ptr(), KFatDirNameSize, apDosEntryName, KFatDirNameSize) == 0) + { + iShortNameCandidates.Remove(i); + break; + } + } + } + } + +/** +Gets a validated short name from the short name candidate pool. + +@param aShortName On return, contains a validated short name if found, otherwise zeroed. +@return TInt Returns KErrNone if a validated short name found successfully, + else KErrNotFound is returned. + Returns KErrNotReady if object is not initialised. +@pre Object should be initialised +*/ +TInt CFatMountCB::XFileCreationHelper::GetValidatedShortName(TShortName& aShortName) const + { + aShortName.Zero(); + + ASSERT(isInitialised); + if (!isInitialised) + return KErrNotReady; + + if (iShortNameCandidates.Count() > 0) + { + aShortName.Copy(iShortNameCandidates[0]); + return KErrNone; + } + + return KErrNotFound; + } + +//----------------------------------------------------------------------------------------- + + +/** + Scan a directory looking for aName. + + @param aTrgtName a name of an object we are looking up in directory + @param anAtt attributes of this object + @param aStartEntryPos on return in case of VFAT entry will contain start position of the VFAT dir. entry + @param aStartEntry on return will contain first VFAT dir entry + @param aDosEntryPos the search will start from this position of dir entry, on return it will contain result DOS entry position, last one for VFAT case + @param aDosEntry on return will contain DOS dir entry (the last one for VFAT case) + @param aFileName in the case of VFAT entry and on success here will be returned a long filename + @param anError This function might leave with this given error code + @param aFileCreationHelper a helper package for file creations + + @return ETrue if extracted entry is VFAT one, EFalse, if it's old DOS-style one + @leave can leave with anError code on error or if the search has reached the end of directory (!) +*/ +TBool CFatMountCB::DoFindL(const TDesC& aTrgtName,TUint anAtt, + TEntryPos& aStartEntryPos,TFatDirEntry& aStartEntry, + TEntryPos& aDosEntryPos,TFatDirEntry& aDosEntry, + TDes& aFileName,TInt anError, + XFileCreationHelper* aFileCreationHelper, + const TLeafDirData& aLeafDirData) const + { + // check that the entry position to be read next is not past the end of the + // root directory. If this is the case then when GetDirEntryL(..) is called + // this will lead to MakeLinAddr(..) leaving with KErrDirFull. + + if (IsRootDir(aDosEntryPos)&&(aDosEntryPos.iPos+StartOfRootDirInBytes()>=RootDirEnd())) + User::Leave(anError);//Allows maximum number of entries in root directory + + __PRINT2(_L("CFatMountCB::DoFindL() drv:%d, %S"),Drive().DriveNumber(),&aTrgtName); + + TInt previousCluster=aDosEntryPos.iCluster; + TUint previousPosition=aDosEntryPos.iPos; + TInt changePreviousCluster=1; + TInt count=0; + + TBool trgNameIsWildCard = EFalse; //-- ETrue if the name we are looking for is a wildcard + TBool trgNameFullySpecified = ETrue; //-- ETrue if the name we are looking for doesn't contain wildcards + + + { + //-- find out if the name we are looking for is a wildcard ("*" or "*.*") + const TInt len = aTrgtName.Length(); + + if(len == 1) + trgNameIsWildCard = (aTrgtName[0] == '*'); + else if(len == 3) + { + _LIT(KAllFiles, "*.*"); + trgNameIsWildCard = (aTrgtName==KAllFiles); + } + + //-- find out if the name we are looking for contains wildcharacters: "*" or "?" + if(trgNameIsWildCard) + trgNameFullySpecified = EFalse; + else + { + for(TInt i=0; iDirCacheInterface() && trgNameFullySpecified && !IsRootDir(aDosEntryPos) && !aFileCreationHelper) + {//-- aName is fully specified, i.e doesn't contain wildcards + + findHelper.InitialiseL(trgtNameNoDot); + + const TBool bMatchFound = DoRummageDirCacheL(anAtt, aStartEntryPos, aStartEntry, aDosEntryPos, aDosEntry, aFileName, findHelper, aFileCreationHelper, aLeafDirData); + if(bMatchFound) + { + return(aStartEntry.IsVFatEntry()); + } + } + //--------------------------------------------------- + + // we need to scan ahead from the mru pos then come back to beginning, if startcluster is provided + TBool scanAhead = EFalse; + // if we have a starting cluster number (and it's not root directory in FAT16/12 case)&& + // we found a lastScanned entry's cluster (and it's not root directory in FAT16/12 case)&& + // if we don't have a starting cluster number, we draw back to original scanning algorithm + if (!IsRootDir(aDosEntryPos) // we don't do forward scanning for root dir & + && aLeafDirData.iClusterNum != 0 // if we have a starting cluster number & + && aLeafDirData.iMRUPos.Cluster() != 0) // if we have a starting cluster number & + { + scanAhead = ETrue; + aDosEntryPos = aLeafDirData.iMRUPos; + } + + TInt numFound = 0; + TEntryPos startPos = aDosEntryPos; + TInt clustNum = aDosEntryPos.Cluster(); + + for (TInt scanCnt = 1; scanCnt <= 2; ++scanCnt) + { + // if we are not scanning ahead, we don't need this outer for loop + if (!scanAhead) + scanCnt++; + + TBool found = EFalse; + + FOREVER //FOREVER2 -- walk through all directory entries in the current directory until find a match or directory end + { + //-- read full directory entry starting from aDosEntryPos. On return aFileName may contain assembled long filename (if the entry is VFAT) + //-- aDosEntry will contain a DOS entry of the directory entry we have read. + aStartEntryPos=aDosEntryPos; + User::LeaveIfError(GetDirEntry(aDosEntryPos, aDosEntry, aStartEntry, aFileName)); + + if (aDosEntry.IsEndOfDirectory()) + {//-- the end of directory reached. + + // if new entry position for adding has not been found yet. + // note aFileCreationHelper may not be initialised for pure file opening operations + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && !aFileCreationHelper->IsNewEntryPosFound()) + { + // if MoveToNextEntryL have gone to the next cluster which is the end of cluster chain, + // we pass the last scanned entry position to AddDirEntryL + if (IsEndOfClusterCh(aDosEntryPos.iCluster)) + { + TInt clusterSize=1<SetEntryAddingPos(dummyPos); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + // or we reached the end of the directory. + else + { + aFileCreationHelper->SetEntryAddingPos(aDosEntryPos); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + } + + // if we are scanning ahead and this is the first scanning, we break out to restart scanning + if (scanAhead && scanCnt == 1) + { + break; // from FOREVER, restart scanning + } + + // if (!scanAhead || scanAhead && scanCnt == 2) + User::Leave(anError); + } + + + // entry space searching for potential new file/directory creation + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && !aFileCreationHelper->IsNewEntryPosFound()) + { + if (!aDosEntry.IsErased() && !aDosEntry.IsGarbage()) + { + numFound = 0; + } + else + { + if (numFound == 0) + { + startPos = aDosEntryPos; + } + numFound++; + if (numFound == aFileCreationHelper->NumOfAddingEntries()) + { + aFileCreationHelper->SetEntryAddingPos(startPos); + aFileCreationHelper->SetIsNewEntryPosFound(ETrue); + } + } + } + + + if (IsRootDir(aDosEntryPos)&&(aDosEntryPos.iPos+StartOfRootDirInBytes()==(RootDirEnd()-KSizeOfFatDirEntry))) + if (aDosEntry.IsErased()) + { + User::Leave(anError);//Allows maximum number of entries in root directory + } + + + const TBool bFileNameEntry = !aDosEntry.IsCurrentDirectory() && !aDosEntry.IsParentDirectory() && !aDosEntry.IsErased() && !aDosEntry.IsGarbage(); + + if (bFileNameEntry && MatchEntryAtt(aDosEntry.Attributes(), anAtt)) + {//-- we have read a filename entry and entry's attributes match required; compare names then. + + if (trgNameIsWildCard) + { + found = ETrue; + break; //-- we were looking for '*' or '*.*', so will be satisfied with any current file name. + } + + + if (aStartEntry.IsVFatEntry()) + {//-- we've read a VFAT entry, aFileName is supposed to contain long filename, aDosEntry - DOS entry for this name. + //-- note: aFileName.Length() may be 0, while DOS entry (short name is OK) in the case of orphaned VFAT entries + + + // we only check short name candidates for long file names with VFAT entries, + // if it is a valid dos name, it will be checked by default + // note, for file creation cases, target name will be always fully specified + if (aFileCreationHelper && aFileCreationHelper->IsInitialised() && trgNameFullySpecified) + { + aFileCreationHelper->CheckShortNameCandidates(aDosEntry.Name().Ptr()); + } + + //-- discard trailing dots from aFileName if present + TPtrC ptrAssembledName = RemoveTrailingDots(aFileName); + + if(ptrAssembledName.MatchF(trgtNameNoDot) != KErrNotFound) + { + found = ETrue; + break; //-- OK, found a match. + } + else if (trgNameFullySpecified) + { + //-- long name assembled by GetDirEntry() doesn't match the target. But if he target name is fully specified, + //-- we need to compare corresponding DOS entries, because VFAT entries may be damaged, while DOS ones are OK. + findHelper.InitialiseL(trgtNameNoDot); + + if(findHelper.MatchDosEntryName(aDosEntry.Name().Ptr())) + { + found = ETrue; + break; //-- DOS entries match, success. + } + } + else if (!trgNameFullySpecified) + {//-- target name contains wildcards, we need to use MatchF with dos name + TBuf8<0x20> dosName8(DosNameFromStdFormat(aDosEntry.Name())); + TBuf<0x20> dosName; + LocaleUtils::ConvertToUnicodeL(dosName, dosName8); //-- convert DOS name to unicode (implies locale settings) + if (dosName.MatchF(trgtNameNoDot)!=KErrNotFound) + { + found = ETrue; + break; + } + } + + + } + else //if (aStartEntry.IsVFatEntry()) + {//-- we've read a legacy FAT entry, so compare DOS entries + findHelper.InitialiseL(trgtNameNoDot); + + if(findHelper.TrgtNameIsLegalDos()) + {//-- we are looking for a legal DOS name + if(trgNameFullySpecified) + {//-- if the target name is fully specified, we can yse binary comparison of the DOS entries + if(findHelper.MatchDosEntryName(aDosEntry.Name().Ptr())) + { + found = ETrue; + break; + } + } + else + {//-- target name contains wildcards, we neeed to use MatchF + TBuf8<0x20> dosName8(DosNameFromStdFormat(aDosEntry.Name())); + TBuf<0x20> dosName; + LocaleUtils::ConvertToUnicodeL(dosName, dosName8); //-- convert DOS name to unicode (implies locale settings) + if (dosName.MatchF(trgtNameNoDot)!=KErrNotFound) + { + found = ETrue; + break; + } + + } + } //if(findHelper.TrgtNameIsLegalDos()) + + } //else if (aStartEntry.IsVFatEntry()) + + } //if (bFileNameEntry && MatchEntryAtt(aDosEntry.Attributes(),anAtt)) + + + // record previous cluster number + clustNum = aDosEntryPos.iCluster; + + // this is the 2nd scanning and we have just passed the pos we started. + if (scanAhead && scanCnt == 2) + { + if (aDosEntryPos.Cluster() == aLeafDirData.iMRUPos.Cluster() + && aDosEntryPos.Pos() >= aLeafDirData.iMRUPos.Pos()) + { + User::Leave(anError); + } + } + + + MoveToNextEntryL(aDosEntryPos); //-- goto the next entry in the directory + + if (IsRootDir(aDosEntryPos)&&(aDosEntryPos.iPos+StartOfRootDirInBytes()>=RootDirEnd())) + { + User::Leave(anError);//Allows maximum number of entries in root directory + } + + + if (!scanAhead || scanCnt == 2) + { + if (aDosEntryPos.iCluster && (aDosEntryPos.iPos <= previousPosition)) + DoCheckFatForLoopsL(aDosEntryPos.iCluster,previousCluster,changePreviousCluster,count); + + previousPosition=aDosEntryPos.iPos; + } + } // FOREVER -- the actual scanning is done inside this loop + + + if (found) + { + break; + } + + // if not found: + // if we have not found in the first scanning and we are doing scanning ahead, + // we need to go back to the starting pos of this dir and scan from start until + // we reach lastscannedPos + if (scanAhead && scanCnt == 1) + { + aDosEntryPos = TEntryPos(aLeafDirData.iClusterNum, 0); + continue; + } + else + { + // there are only two exits: either found or reached end of dir in the 1st scanning + ASSERT(0); + break; + } + } // for (TInt scanCnt = 1; scanCnt <= 2; ++scanCnt) + + //--------------------------------------------------- + if (iRawDisk->DirCacheInterface() && aDosEntryPos.Cluster()) + { + TInt64 mruPos = MakeLinAddrL(aDosEntryPos); + iRawDisk->DirCacheInterface()->MakePageMRU(mruPos); + + // only update the leaf dir cache when the original cache index is provided + if (aLeafDirData.iClusterNum) + { + iLeafDirCache->UpdateMRUPos(TLeafDirData(aLeafDirData.iClusterNum, aDosEntryPos)); + } + } + + return (aStartEntry.IsVFatEntry()); + } + +//----------------------------------------------------------------------------------------- +/** + Locate an directory entry entry from its full path name. + + @param aName a name of an object we are looking for + @param anAtt attributes of this object + @param anEntry on return will contain first VFAT dir entry + @param anEntryPos on return in case of VFAT entry will contain start position of the VFAT dir. entry + + @leave can leave with KErrNotFound if the search has reached the end of directory +*/ +void CFatMountCB::FindEntryStartL(const TDesC& aName,TUint anAtt,TFatDirEntry& anEntry,TEntryPos& anEntryPos) const + { + __PRINT(_L("CFatMountCB::FindEntryStartL()")); + TInt namePos=aName.LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + TFileName fileName; + TLeafDirData leafDir; + TEntryPos dosEntryPos(FindLeafDirL(aName.Left(namePos),leafDir),0); + TFatDirEntry dosEntry; + + DoFindL(aName.Mid(namePos),anAtt,anEntryPos,anEntry,dosEntryPos,dosEntry,fileName,KErrNotFound,NULL,leafDir); + } + + +//----------------------------------------------------------------------------------------- + +/** + Locate an directory entry entry from its full path name. + + @param aName a name of an object we are looking for + @param anAtt attributes of this object + @param anEntry on return will contain first VFAT dir entry + @param anEntryPos on return in case of VFAT entry will contain start position of the VFAT dir. entry + + @leave can leave with KErrNotFound if the search has reached the end of directory +*/ +void CFatMountCB::FindEntryStartL(const TDesC& aName,TUint anAtt,TFatDirEntry& anEntry,TEntryPos& anEntryPos,XFileCreationHelper* aFileCreationHelper) const + { + __PRINT(_L("CFatMountCB::FindEntryStartL()")); + TInt namePos=aName.LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + TFileName fileName; + TLeafDirData leafDir; + TEntryPos dosEntryPos(FindLeafDirL(aName.Left(namePos),leafDir),0); + TFatDirEntry dosEntry; + DoFindL(aName.Mid(namePos),anAtt,anEntryPos,anEntry,dosEntryPos,dosEntry,fileName,KErrNotFound,aFileCreationHelper,leafDir); + } + +//----------------------------------------------------------------------------------------- +void CFatMountCB::FindDosNameL(const TDesC& aName,TUint anAtt,TEntryPos& aDosEntryPos,TFatDirEntry& aDosEntry,TDes& aFileName,TInt anError) const +// +// Scan a directory looking for aName. +// aCluster and anEntryAddr give the location of the entry. +// + { + + __PRINT(_L("CFatMountCB::FindDosNameL()")); + TEntryPos startPos; + TFatDirEntry startEntry; + + TLeafDirData leafDir; // leaf dir data is zero initialized, no scannig ahead + DoFindL(aName,anAtt,startPos,startEntry,aDosEntryPos,aDosEntry,aFileName,anError,NULL,leafDir); + } +//----------------------------------------------------------------------------------------- + +void CFatMountCB::AddDirEntryL(TEntryPos& aPos,TInt aNumOfEntries) +// +// Find space for a new directory entry. Leave KErrEof if no space +// + { + + __PRINT(_L("CFatMountCB::AddDirEntryL")); + TInt numFound=0; + TFatDirEntry entry; + TEntryPos startPos(RootIndicator(),0); + TInt clusterNum=aPos.iCluster; + FOREVER + { + ReadDirEntryL(aPos,entry); + if (entry.IsEndOfDirectory()) + break; + if (!entry.IsErased() && !entry.IsGarbage()) + numFound=0; + else + { + if (numFound==0) + startPos=aPos; + numFound++; + if (numFound==aNumOfEntries) + { + aPos=startPos; + return; + } + } + clusterNum=aPos.iCluster; + MoveToNextEntryL(aPos); + if (IsRootDir(aPos)&&(StartOfRootDirInBytes()+aPos.iPos==RootDirEnd())) + // No end of directory marker at end of root directory + User::Leave(KErrDirFull); + } + + TUint clusterSize=1<RootDirEnd()) + User::Leave(KErrDirFull); + else + return; + } + + if (eofPos.iPos==clusterSize) + return; // No need to allocate + if (eofPos.iPos>clusterSize) + { + TInt numNeeded=eofPos.iPos>>ClusterSizeLog2(); + if(IsRuggedFSys()) + { + ExtendClusterListZeroedL(numNeeded,eofPos.iCluster); + } + else + { + FAT().ExtendClusterListL(numNeeded,eofPos.iCluster); + ZeroDirClusterL(eofPos.iCluster); + } + + eofPos.iPos-=numNeeded<=1); + + for(TInt i=0; i vBuf(KMaxVFatEntryName); + aDosEntry.ReadVFatEntry(vBuf); + + TInt vLength=vBuf.Locate('\0'); + if (vLength==KErrNotFound) + vLength=KMaxVFatEntryName; + + vBuf.SetLength(vLength); + + const TInt nameLen = vLength+KMaxVFatEntryName*(count-1); + if(nameLen <= 0 || nameLen > KMaxFileName) + return EFalse; //-- wrong long file name length, consider VFAT entry as orphaned + + aLongFileName.SetLength(nameLen); + + const TUint8 entryCheckSum = aDosEntry.CheckSum(); //-- check sum from the 1st VFat entry + + while (count--) + { + TPtr fileNamePtr(&aLongFileName[0]+KMaxVFatEntryName*count,aLongFileName.Length()-KMaxVFatEntryName*count); + fileNamePtr.Copy(vBuf); + if (count==0) + break; //-- all VFat entries read, only DOS entry remained + + MoveToNextEntryL(aPos); + ReadDirEntryL(aPos,aDosEntry); + + //-- check if it is correct VFat entry. + //-- if not, this is the "orphaned" entry and will be ignored + if(!aDosEntry.IsVFatEntry() || aDosEntry.IsErased() || entryCheckSum != aDosEntry.CheckSum() || aDosEntry.NumFollowing() != count) + return EFalse; //-- bad VFAT entry + + aDosEntry.ReadVFatEntry(vBuf); + } + + if (IsRootDir(aPos)&&(aPos.iPos+StartOfRootDirInBytes()==(RootDirEnd()-KSizeOfFatDirEntry))) + return ETrue;//Allows maximum number of entries in root directory + + //-- read the last, DOS FAT entry + MoveToNextEntryL(aPos); + ReadDirEntryL(aPos,aDosEntry); + + //-- check if it is corect + if(aDosEntry.IsEndOfDirectory() || aDosEntry.IsErased() || aDosEntry.IsVFatEntry()) + return EFalse; //-- Bad DOS entry + + //-- verify ChechSum here if it is incorrect, use DOS name only + const TUint8 calcNameChkSum = CalculateShortNameCheckSum(aDosEntry.Name()); + if(calcNameChkSum != entryCheckSum) + { + aLongFileName.SetLength(0);//-- don't use long filename + __PRINT2(_L("CFatMountCB::GetDirEntryL() CheckSum mismatch: VFat:0x%x, DOS:0x%d"),entryCheckSum, calcNameChkSum); + } + + return ETrue; + } + + +/** + Read a number of VFAT entries from the directory file. + for parameters see DoGetDirEntryL() + + @return KErrNone if everything is OK, system wide error code otherwise + +*/ +TInt CFatMountCB::GetDirEntry(TEntryPos& aPos,TFatDirEntry& aDosEntry,TFatDirEntry& aStartEntry,TDes& aLongFileName) const + { + + TBool bEntryOK=ETrue; + TRAPD(nErr, bEntryOK = DoGetDirEntryL(aPos, aDosEntry, aStartEntry, aLongFileName)); + + if(nErr !=KErrNone) + return nErr; + + if(!bEntryOK) + {//-- DoGetDirEntryL could not assemble whole VFat entry, probably some parts of it are damaged. + //-- consider it as an "orphaned" entry and skip + aDosEntry.iData[0] = 0xFF; // Mark entry as garbage + aLongFileName.SetLength(0); // No long filename + } + + return KErrNone; + } + +void CFatMountCB::MoveToNextEntryL(TEntryPos& aPos) const +// +// If anEntry is at the end of the cluster, and we are not the root dir, +// move it to the next in the list. +// + { + +// __PRINT(_L("CFatMountCB::MoveToNextEntryL")); + if (IsEndOfClusterCh(aPos.iCluster)) + return; + const TUint temp = 1<= UsableClusters()+KFatFirstSearchCluster) + User::Leave(KErrCorrupt); + + TBuf8 uidBuf; + iRawDisk->ReadCachedL(FAT().DataPositionInBytes(aCluster),sizeof(TCheckedUid),uidBuf); + __ASSERT_DEBUG(uidBuf.Length()==sizeof(TCheckedUid),Fault(EFatReadUidFailed)); + TCheckedUid uid(uidBuf); + anEntry.iType=uid.UidType(); + } + +//----------------------------------------------------------------------------------------- + +/** + Read file section without opening this file on a file server side. + + @param aName file name; all trailing dots from the name will be removed + @param aFilePos start read position within a file + @param aLength how many bytes to read; on return will be how many bytes actually read + @param aDes local buffer desctriptor + @param aMessage from file server, used to write data to the buffer in different address space. + + @leave on media read error +*/ +void CFatMountCB::ReadSectionL(const TDesC& aName,TInt aPos,TAny* aTrg,TInt aLength,const RMessagePtr2& aMessage) + { + __PRINT4(_L("CFatMountCB::ReadSectionL, drv:%d, pos:%d, len:%d, FN:%S"), DriveNumber(), aPos, aLength, &aName); + + CheckStateConsistentL(); + + TEntryPos dosEntryPos(RootIndicator(),0); + TFatDirEntry dosEntry; + TFileName fileName; + + + TInt namePos=RemoveTrailingDots(aName).LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + TLeafDirData leafDir; + dosEntryPos.iCluster=FindLeafDirL(RemoveTrailingDots(aName).Left(namePos), leafDir); + dosEntryPos.iPos=0; + TEntryPos startPos; + TFatDirEntry startEntry; + DoFindL(RemoveTrailingDots(aName).Mid(namePos),KEntryAttMaskSupported, + startPos,startEntry,dosEntryPos,dosEntry, + fileName,KErrNotFound, + NULL, + leafDir); + +// Check that reading from aPos for aLength lies within the file +// if aPos is within the file, and aLength is too long, read up to EOF +// If aPos is beyond the end of the file, return a zero length descriptor + + TUint32 fileSize = dosEntry.Size(); + if ((TUint)aPos>=fileSize) + User::Leave(KErrEof); + + if ((TUint)(aPos+aLength)>fileSize) + aLength=fileSize-aPos; + + TInt cluster=StartCluster(dosEntry); + TInt pos = aPos; + + TInt endCluster; + TInt clusterSize=1<>ClusterSizeLog2()); + + // Read data + FOREVER + { + // Get the maximum number of clusters that can be read contiguously + TInt clusterListLen=FAT().CountContiguousClustersL(cluster,endCluster,maxClusters); + __ASSERT_DEBUG(clusterListLen>0,Fault(EReadFileSectionFailed)); + + // If start position within this block, then read some data + if (pos<(clusterListLen<0,Fault(EReadFileSectionFailed)); + TInt64 dataAddress=(FAT().DataPositionInBytes(cluster))+pos; + iRawDisk->ReadL(dataAddress,readLength,aTrg,aMessage,readTotal); + readTotal += readLength; + + if (readTotal == aLength) + return; + + pos += readLength; + } + + // Get the next cluster in file + pos-=(clusterListLen<FAT().GetNextClusterL(endCluster); + __ASSERT_DEBUG(remainingClusters,Fault(EReadFileSectionFailed)); + cluster=endCluster; + } + } + + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::RawReadL(TInt64 aPos,TInt aLength,const TAny* aTrg,TInt anOffset,const RMessagePtr2& aMessage) const +// +// Read aLength of data from disk directly to thread relative descriptor +// + { + iRawDisk->ReadL(aPos,aLength,aTrg,aMessage,anOffset); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::RawWriteL(TInt64 aPos,TInt aLength,const TAny* aSrc,TInt anOffset,const RMessagePtr2& aMessage) +// +// Write aLength of data from thread relative descriptor to disk +// + { + CheckWritableL(); + + //-- check if we are trying to write to the FAT directly and wait until FAT scan thread finishes in this case. + FAT().RequestRawWriteAccess(aPos, aLength); + + iRawDisk->WriteL(aPos,aLength,aSrc,aMessage,anOffset); + //-- Note: FAT directory cache will be invalidated in MountL() + } + +//----------------------------------------------------------------------------------------- +/** + This method must be used when writing to the FAT directory file. + If FAT directory cache is present on this drive, it will be used. + @param aPos entry media position + @param aDes data descriptor +*/ +void CFatMountCB::DirWriteL(const TEntryPos& aPos,const TDesC8& aDes) + { + CheckWritableL(); + const TInt64 posAddr=MakeLinAddrL(aPos); + + if(!iRawDisk->DirCacheInterface()) + { + iRawDisk->WriteCachedL(posAddr,aDes); + } + else + {//-- if there is an interface to the FAT directory cache, use it + iRawDisk->DirCacheInterface()->WriteL(posAddr, aDes); + } + } + +//----------------------------------------------------------------------------------------- + +/** + This method must be used when reading from the FAT directory file. + If FAT directory cache is present on this drive, it will be used. + + @param aPos entry media position + @param aLength how many bytes to read + @param aDes input data descriptor +*/ +void CFatMountCB::DirReadL(const TEntryPos& aPos, TInt aLength, TDes8& aDes) const + { + const TInt64 posAddr=MakeLinAddrL(aPos); + + if(!iRawDisk->DirCacheInterface()) + { + iRawDisk->ReadCachedL(posAddr,aLength,aDes); + } + else + {//-- if there is an interface to the FAT directory cache, use it + iRawDisk->DirCacheInterface()->ReadL(posAddr, aLength, aDes); + } + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::WriteDirEntryL(const TEntryPos& aPos,const TFatDirEntry& aDirEntry) +// +// Write a FAT directory entry to disk. +// Assumes sufficient space has been created for it by AddDirEntry. +// + { + + __PRINT(_L("CFatMountCB::WriteDirEntryL")); + + //-- use special interface to access FAT directory file + DirWriteL(aPos,TPtrC8((TUint8*)&aDirEntry,KSizeOfFatDirEntry)); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::EraseDirEntryL(const TEntryPos& aPos) +// +// Mark a dir entry as erased +// + { + + __PRINT(_L("CFatMountCB::EraseDirEntryL")); + if(!iLeafDirCache && iLastLeafDir) + iLastLeafDir->Des().SetLength(0); + + //-- use special interface to access FAT directory file + DirWriteL(aPos,TPtrC8((TUint8*)&KEntryErasedMarker,sizeof(TUint8))); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::ReadDirEntryL(const TEntryPos& aPos,TFatDirEntry& aDirEntry) const +// +// Read a FAT directory entry to disk +// + { + +// __PRINT(_L("CFatMountCB::ReadDirEntryL")); + if (IsEndOfClusterCh(aPos.iCluster)) + { + aDirEntry.InitZ(); + return; + } + TPtr8 buf=TPtr8((TUint8*)&aDirEntry,KSizeOfFatDirEntry); + + //-- use special interface to access FAT directory file + DirReadL(aPos,KSizeOfFatDirEntry,buf); + } + +//----------------------------------------------------------------------------------------- + +/** + Enlarge the disk's size. + This method can be called only for variable size media, i.e. RAM drive + + @param aSize size increment (bytes) +*/ +void CFatMountCB::EnlargeL(TInt aSize) + { + __PRINT2(_L("CFatMountCB::EnlargeL by 0x%x currentsize=0x%x"),aSize,iSize); + + ASSERT(iRamDrive); + + TInt maxSize; + if (HAL::Get(HAL::EMaxRAMDriveSize, maxSize) == KErrNone && iSize + aSize > maxSize) + User::Leave(KErrDiskFull); + User::LeaveIfError(LocalDrive()->Enlarge(aSize)); + iSize+=aSize; + + if (&FAT()) + { + FAT().InitializeL(); + } + + if (&RawDisk()) + { + RawDisk().InitializeL(); + } + + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::ReduceSizeL(TInt aPos,TInt aLength) +// +// Reduce the disk's size +// + { + + __PRINT2(_L("CFatMountCB::ReduceSizeL aPos=0x%x aLength=0x%x"),aPos,aLength); + User::LeaveIfError(LocalDrive()->ReduceSize(aPos,aLength)); + iSize-=aLength; + } + +//----------------------------------------------------------------------------------------- + +TInt64 CFatMountCB::MakeLinAddrL(const TEntryPos& aPos) const +// +// Convert cluster/position into linear address +// + { + + //__PRINT2(_L("CFatMountCB::MakeLinAddrL, cl:%d, pos:%d"), aPos.iCluster, aPos.iPos); + if (!IsRootDir(aPos)) + { + TInt relPos=ClusterRelativePos(aPos.iPos); + return FAT().DataPositionInBytes(aPos.iCluster)+relPos; + } + if (aPos.iPos+StartOfRootDirInBytes()>=RootDirEnd()) + User::Leave(KErrDirFull); // Past last root dir entry + return StartOfRootDirInBytes()+aPos.iPos; + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::GetShortNameL(const TDesC& aLongName,TDes& aShortName) +// +// Get the short name associated with a long file name +// + { + __PRINT(_L("CFatMountCB::GetShortNameL")); + TEntryPos firstEntryPos(RootIndicator(),0); + TFatDirEntry firstEntry; + FindEntryStartL(aLongName,KEntryAttMaskSupported,firstEntry,firstEntryPos); + MoveToDosEntryL(firstEntryPos,firstEntry); + TBuf8<0x20> dosName(DosNameFromStdFormat(firstEntry.Name())); + LocaleUtils::ConvertToUnicodeL(aShortName, dosName); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::GetLongNameL(const TDesC& aShortName,TDes& aLongName) +// +// Get the long name associated with a short file name +// + { + __PRINT(_L("CFatMountCB::GetLongNameL")); + TEntryPos pos(RootIndicator(),0); + TFatDirEntry entry; + const TInt namePos=aShortName.LocateReverse(KPathDelimiter)+1; // There is always a path delimiter + const TPtrC shortNameWithoutPathDelimiter(aShortName.Mid(namePos)); + __ASSERT_ALWAYS(shortNameWithoutPathDelimiter.Length()<=12,User::Leave(KErrBadName)); + + TLeafDirData leafDir; + pos.iCluster=FindLeafDirL(aShortName.Left(namePos), leafDir); + FOREVER + { + TFatDirEntry startEntry; + User::LeaveIfError(GetDirEntry(pos,entry,startEntry,aLongName)); + if (entry.IsEndOfDirectory()) + User::Leave(KErrNotFound); + TBool entryIsVFat=EFalse; + if (startEntry.IsVFatEntry()) + entryIsVFat=ETrue; + if (!entry.IsParentDirectory() && !entry.IsCurrentDirectory() && !entry.IsGarbage() && !entry.IsErased()) + { + TBuf8<0x20> entryName8(DosNameFromStdFormat(entry.Name())); + TBuf<0x20> entryName; + LocaleUtils::ConvertToUnicodeL(entryName, entryName8); + if (shortNameWithoutPathDelimiter.MatchF(entryName)!=KErrNotFound) + { + if (entryIsVFat==EFalse) + aLongName=shortNameWithoutPathDelimiter; + return; + } + } + MoveToNextEntryL(pos); + } + } + + + +//----------------------------------------------------------------------------------------- + +/** + Extend a file or directory, zeroing cluster chain and flushing after every write to FAT. + This method is called for rugged FAT only. + for parameters see CFatTable::ExtendClusterListL +*/ +void CFatMountCB::ExtendClusterListZeroedL(TInt aNumber,TInt& aCluster) + { + __PRINT(_L("CFatMountCB::ExtendClusterListZeroedL")); + __ASSERT_DEBUG(aNumber>0,Fault(EFatBadParameter)); + + while(aNumber && FAT().GetNextClusterL(aCluster)) + aNumber--; + + //-- request aNumber free clusters from the FAT, this request may wait until FAT scan thread counted enough free clusters if it is running. + if(!FAT().RequestFreeClusters(aNumber)) + { + __PRINT(_L("CFatMountCB::ExtendClusterListL - leaving KErrDirFull")); + User::Leave(KErrDiskFull); + } + while (aNumber--) + { + TInt freeCluster=FAT().AllocateSingleClusterL(aCluster); + FAT().FlushL(); + ZeroDirClusterL(freeCluster); + FAT().WriteL(aCluster,freeCluster); + FAT().FlushL(); + aCluster=freeCluster; + } + } + +//----------------------------------------------------------------------------------------- + +#if defined(_DEBUG) +TInt CFatMountCB::ControlIO(const RMessagePtr2& aMessage,TInt aCommand,TAny* aParam1,TAny* aParam2) +// +// Debug function +// + { + if(aCommand>=EExtCustom) + { + if(LocalDrive()) + return LocalDrive()->ControlIO(aMessage,aCommand-EExtCustom,aParam1,aParam2); + else + return KErrNotSupported; + } + switch(aCommand) + { + case ECriticalWriteFailOn: + { + TInt r; + TInt16 args[2]; + TPtr8 des((TUint8*)args,4,4); + TRAP(r,aMessage.ReadL(2,des,0)); + if(r!=KErrNone) + return(r); + SetWriteFail(ETrue); + SetWriteFailCount(args[0]); + SetWriteFailError(args[1]); + break; + } + case ECriticalWriteFailOff:SetWriteFail(EFalse);break; + case ERuggedFSysOn: SetRuggedFSys(ETrue);break; + case ERuggedFSysOff: SetRuggedFSys(EFalse);break; + case EIsRuggedFSys: + { + TInt r; + TUint8 val = (IsRuggedFSys()!=0); // val = 0 or 1 for false/true + TPtr8 pVal(&val,1,1); + TRAP(r,aMessage.WriteL(2,pVal,0)); + if(r!=KErrNone) + return(r); + break; + } + case ELocalTimeForRemovableMediaOn: + { + FatFileSystem().SetUseLocalTime(ETrue); + break; + } + case ELocalTimeForRemovableMediaOff: + { + FatFileSystem().SetUseLocalTime(EFalse); + break; + } + case ELocalTimeUsedOnRemovableMedia: + { + TBool flag = FatFileSystem().GetUseLocalTime(); + TPckgC flagPckg(flag); + TInt r = aMessage.Write(2, flagPckg); + if(r!=KErrNone) + return r; + break; + } + case ECreationTime: + { + CheckStateConsistentL(); + + TEntryPos firstEntryPos(RootIndicator(),0); + TFatDirEntry firstEntry; + //RFs::ControlIO restricts you to use narrow descriptors + //so convert narrow back to wide. + TBuf8 fileNameNarrow; + aMessage.Read(2, fileNameNarrow); + + TFileName fileNameWide; + fileNameWide.Copy(fileNameNarrow); + + //find the long file name entry + TRAPD(r, FindEntryStartL(fileNameWide,KEntryAttMaskSupported,firstEntry,firstEntryPos) ); + if(r!=KErrNone) + return(r); + //Find the corresponding 8.3 short name entry, for metadata + MoveToDosEntryL(firstEntryPos,firstEntry); + TTime creationTime=0; + TPckg timePckg(creationTime); + SFatDirEntry* sEntry = reinterpret_cast(firstEntry.iData); + creationTime = DosTimeToTTime(sEntry->iTimeC, sEntry->iDateC); + r = aMessage.Write(3, timePckg); + if(r!=KErrNone) + return r; + break; + } + case EDisableFATDirCache: + { + MWTCacheInterface* pDirCache = iRawDisk->DirCacheInterface(); + TUint32 KEDisableFATDirCache = CDynamicDirCache::EDisableCache; + pDirCache->Control(KEDisableFATDirCache, (TUint32) aParam1, NULL); + break; + } + case EDumpFATDirCache: + { + MWTCacheInterface* pDirCache = iRawDisk->DirCacheInterface(); + TUint32 KEDumpFATDirCache = CDynamicDirCache::EDumpCache; + pDirCache->Control(KEDumpFATDirCache, 0, NULL); + break; + } + case EFATDirCacheInfo: + { + MWTCacheInterface* pDirCache = iRawDisk->DirCacheInterface(); + TUint32 KEFATDirCacheInfo = CDynamicDirCache::ECacheInfo; + pDirCache->Control(KEFATDirCacheInfo, 0, NULL); + break; + } + + + default: return(KErrNotSupported); + } + return(KErrNone); + } +#else +TInt CFatMountCB::ControlIO(const RMessagePtr2& /*aMessage*/,TInt /*aCommand*/,TAny* /*aParam1*/,TAny* /*aParam2*/) + {return(KErrNotSupported);} +#endif + + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::Lock(TMediaPassword& aOld,TMediaPassword& aNew,TBool aStore) +// +// lock media device +// + { + __PRINT(_L("CFatMountCB::Lock")); + TInt r=CreateDrive(Drive().DriveNumber()); + if (r!=KErrNone) + return r; + + TBusLocalDrive* local; + r=LocalDrive()->GetLocalDrive(local); + if (r!=KErrNone) + return r; + +#ifdef _LOCKABLE_MEDIA + if(local->Status()==KErrLocked) + local->Status() = KErrNotReady; +#endif + r=local->SetPassword(aOld,aNew,aStore); + if(r==KErrNone&&aStore) + WritePasswordData(); + return(r); + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::Unlock(TMediaPassword& aPassword,TBool aStore) +// +// Unlock media device +// + { + __PRINT(_L("CFatMountCB::Unlock")); + TInt r=CreateDrive(Drive().DriveNumber()); + if (r!=KErrNone) + return r; + + TBusLocalDrive* local; + r=LocalDrive()->GetLocalDrive(local); + if (r!=KErrNone) + return r; + +#ifdef _LOCKABLE_MEDIA + if(local->Status()==KErrLocked) + local->Status() = KErrNotReady; +#endif + r=local->Unlock(aPassword,aStore); + if(r==KErrNone&&aStore) + WritePasswordData(); + return(r); + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::ClearPassword(TMediaPassword& aPassword) +// +// Clear password from media device +// + { + __PRINT(_L("CFatMountCB::ClearPassword")); + TInt r=CreateDrive(Drive().DriveNumber()); + if (r!=KErrNone) + return r; + + TBusLocalDrive* local; + r=LocalDrive()->GetLocalDrive(local); + if (r!=KErrNone) + return r; + +#ifdef _LOCKABLE_MEDIA + if(local->Status()==KErrLocked) + local->Status() = KErrNotReady; +#endif + r=local->Clear(aPassword); + if(r==KErrNone) + WritePasswordData(); + return(r); + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::ErasePassword() +// +// Forcibly erase the password from a media device +// + { + __PRINT(_L("CFatMountCB::ErasePassword")); + + TInt r=CreateDrive(Drive().DriveNumber()); + if (r!=KErrNone) + return r; + + TBusLocalDrive* local; + r=LocalDrive()->GetLocalDrive(local); + if (r!=KErrNone) + return r; + +#ifdef _LOCKABLE_MEDIA + if(local->Status()==KErrLocked) + local->Status() = KErrNotReady; +#endif + r=local->ErasePassword(); + if(r==KErrNone) + { + // ...media change to ensure a fresh remount the drive + r = local->ForceRemount(0); + local->Status() = KErrNotReady; + WritePasswordData(); + } + return(r); + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::ForceRemountDrive(const TDesC8* aMountInfo,TInt aMountInfoMessageHandle,TUint aFlags) +// +// Force a remount of the drive +// + { + __PRINT(_L("CFatMountCB::ForceRemountDrive")); + TInt r=CreateDrive(Drive().DriveNumber()); + if (r==KErrNone) + r=LocalDrive()->SetMountInfo(aMountInfo,aMountInfoMessageHandle); + if (r==KErrNone) + r=LocalDrive()->ForceRemount(aFlags); + return(r); + } + +//----------------------------------------------------------------------------------------- + +void CFatMountCB::WritePasswordData() +// +// Write store password data to disk +// + { + __PRINT(_L("CFatMountCB::WritePasswordData")); + TBuf mediaPWrdFile(KMediaPWrdFile); + mediaPWrdFile[0] = (TUint8) RFs::GetSystemDriveChar(); + __PRINT1TEMP(_L("disk file = %S"),mediaPWrdFile); + TBusLocalDrive& local=GetLocalDrive(Drive().DriveNumber()); + TInt length=local.PasswordStoreLengthInBytes(); + if(length==0) + { + WriteToDisk(mediaPWrdFile,_L8("")); + return; + } + HBufC8* hDes=HBufC8::New(length); + if(hDes==NULL) + return; + TPtr8 pDes=hDes->Des(); + TInt r=local.ReadPasswordData(pDes); + if(r==KErrNone) + WriteToDisk(mediaPWrdFile,pDes); + delete hDes; + } + +//----------------------------------------------------------------------------------------- + +/** +Trim trailing spaces of volume label descriptor and adjust its length +*/ +void CFatMountCB::TrimVolumeLabel(TDes8& aLabel) const + { + // Locate first '\0' + TInt nullPos = aLabel.Locate('\0'); + if (nullPos == KErrNotFound) + nullPos = KVolumeLabelSize; + + // Trim trailing spaces + TInt i; + for (i=nullPos-1; i>=0; --i) + if (aLabel[i] != 0x20) + break; + aLabel.SetLength(i+1); + } + +//----------------------------------------------------------------------------------------- + +/** +Searches for the volume label file + +@param aLabel The name of the volume label file returned upon successful search +@return KErrNone if it finds the volume label file, otherwise KErrNotFound +*/ +TInt CFatMountCB::ReadVolumeLabelFile(TDes8& aLabel) + { + __PRINT(_L("+CFatMountCB::ReadVolumeLabelFile")); + TEntryPos pos(RootIndicator(),0); + TFatDirEntry entry; + TRAPD(r, FindVolumeLabelFileL(aLabel, pos, entry)); + __PRINT1(_L("-CFatMountCB::ReadVolumeLabelFile: %d"),r); + return r; + } + +//----------------------------------------------------------------------------------------- + +/** +Creates or updates the volume label file with name aNewName + +@param aNewName The new name for the volume label file +*/ +void CFatMountCB::WriteVolumeLabelFileL(const TDesC8& aNewName) + { + __PRINT1(_L("+CFatMountCB::WriteVolumeLabelFileL: [%S]"), &aNewName); + TEntryPos pos(RootIndicator(),0); + TFatDirEntry entry; + + TBuf8 oldName; + TRAPD(r, FindVolumeLabelFileL(oldName, pos, entry)); + + if( KErrNone == r ) + { + // Found existing volume label file, rename or delete + if(oldName == aNewName) + { + __PRINT(_L("-CFatMountCB::WriteVolumeLabelFileL: found: names match")); + return; + } + else + { + if(aNewName.Length() == 0) + { + // delete the volume label file + __PRINT(_L("CFatMountCB::WriteVolumeLabelFileL: found: delete")); + EraseDirEntryL(pos, entry); + } + else + { + __PRINT(_L("CFatMountCB::WriteVolumeLabelFileL: found: replace")); + entry.SetName(aNewName); + WriteDirEntryL(pos, entry); + } + FAT().FlushL(); + } + } + else if( KErrNotFound == r ) + { + // Not found, need to create if aNewName is not empty + // Windows allows a volume label file to have the same name as + // an existing file or directory + if(aNewName.Length() > 0) + { + __PRINT(_L("CFatMountCB::WriteVolumeLabelFileL: not found: create")); + TEntryPos dirPos(RootIndicator(),0); + AddDirEntryL(dirPos,1); + TFatDirEntry fatDirEntry; + fatDirEntry.SetName(aNewName); + fatDirEntry.SetAttributes(KEntryAttVolume); + + TTime now; + now.UniversalTime(); + fatDirEntry.SetTime(now, TimeOffset() ); + fatDirEntry.SetStartCluster(0); + fatDirEntry.SetSize(0); + WriteDirEntryL(dirPos, fatDirEntry); + FAT().FlushL(); + } + } + else + { + // Some other error + User::Leave(r); + } + } + + +//----------------------------------------------------------------------------------------- + +/** +Scans the root directory for a volume label file. Leaves with an error if not found + +@param aLabel Name of the volume label file upon successful search +@param aDosEntryPos Pointer to position of the volume label file upon successful search +@param aDosEntry Contains the entry for the volume label file upon successful search +*/ +void CFatMountCB::FindVolumeLabelFileL(TDes8& aLabel, TEntryPos& aDosEntryPos, TFatDirEntry& aDosEntry) + { + __PRINT(_L("+CFatMountCB::FindVolumeLabelFileL")); + + if(IsRootDir(aDosEntryPos) && (aDosEntryPos.iPos+StartOfRootDirInBytes()>=RootDirEnd())) + { + __PRINT(_L("-CFatMountCB::FindVolumeLabelFileL: abort, exceeds root")); + User::Leave(KErrNotFound); // Allows maximum number of entries in root directory + } + + TInt previousCluster= aDosEntryPos.iCluster; + TUint previousPosition= aDosEntryPos.iPos; + TInt changePreviousCluster=1; + TInt count=0; + + TFatDirEntry startEntry; + TFileName dummyLongName; + + FOREVER + { +#ifdef _DEBUG + const TInt e= GetDirEntry(aDosEntryPos, aDosEntry, startEntry, dummyLongName); + __PRINT1(_L("CFatMountCB::FindVolumeLabelFileL: GetDir %d"), e); + User::LeaveIfError(e); +#else + User::LeaveIfError(GetDirEntry(aDosEntryPos, aDosEntry, startEntry, dummyLongName)); +#endif + if(aDosEntry.IsEndOfDirectory()) + { + __PRINT(_L("-CFatMountCB::FindVolumeLabelFileL: end of dir")); + User::Leave(KErrNotFound); + } + if(IsRootDir(aDosEntryPos) && (aDosEntryPos.iPos+StartOfRootDirInBytes()==(RootDirEnd()-KSizeOfFatDirEntry))) + { + if(aDosEntry.IsErased()) + { + __PRINT(_L("-CFatMountCB::FindVolumeLabelFileL: erased end of root")); + User::Leave(KErrNotFound); //Allows maximum number of entries in root directory + } + } + if(!aDosEntry.IsCurrentDirectory() && !aDosEntry.IsParentDirectory() && !aDosEntry.IsErased() && !aDosEntry.IsGarbage()) + { + if(aDosEntry.Attributes() & KEntryAttVolume) + { + aLabel = aDosEntry.Name(); +#ifdef _DEBUG + dummyLongName.Copy(aLabel); + __PRINT1(_L("-CFatMountCB::FindVolumeLabelFileL: found [%S]"), &dummyLongName); +#endif + break; + } + } + MoveToNextEntryL(aDosEntryPos); + if(IsRootDir(aDosEntryPos) && (aDosEntryPos.iPos+StartOfRootDirInBytes()>=RootDirEnd())) + { + __PRINT(_L("-CFatMountCB::FindVolumeLabelFileL: passed end of root")); + User::Leave(KErrNotFound); //Allows maximum number of entries in root directory + } + if(aDosEntryPos.iCluster && (aDosEntryPos.iPos <= previousPosition)) + { + DoCheckFatForLoopsL(aDosEntryPos.iCluster, previousCluster, changePreviousCluster, count); + } + previousPosition=aDosEntryPos.iPos; + } + } + +//----------------------------------------------------------------------------------------- + +/** +Read volume label from disk and trim trailing 0x20 & 0x00 characters +*/ +void CFatMountCB::GetVolumeLabelFromDiskL(const TFatBootSector& aBootSector) + { + // Read volume label as 8 bit descriptor + TBuf8 volName8; + TInt r = ReadVolumeLabelFile(volName8); + if(r != KErrNone) // No volume label file in root directory + volName8 = aBootSector.VolumeLabel(); + TrimVolumeLabel(volName8); + + TBuf16 volName16; + LocaleUtils::ConvertToUnicodeL(volName16, volName8); + SetVolumeName(volName16.AllocL()); + } + + +//----------------------------------------------------------------------------------------- + +/** +Populates iMap member of aInfo with contiguous block group maps. + +@param aPos Start position for a desired section of the file. +@param sLength Length of the desired data to produce the block map for. +@param aInfo A structure describing a group of block maps. +*/ +void CFatMountCB::BlockMapReadFromClusterListL(TEntryPos& aPos, TInt aLength, SBlockMapInfo& aInfo) + { + __PRINT(_L("CFatMountCB::BlockMapReadFromClusterListL")); + __ASSERT_ALWAYS(aPos.Cluster()>=KFatFirstSearchCluster,User::Leave(KErrCorrupt)); + TBlockMapEntry blockMapEntry; + + TUint i = 0; + TInt clusterRelativePos; + TInt maxClusters; + TInt endCluster; + TInt clusterListLen; + TInt readLength; + TInt temp; + TInt currentPos; + TLocalDriveCapsBuf caps; + TInt r; + TInt64 realPosition = 0; + + do + { + currentPos = aPos.iPos; + temp = currentPos>>ClusterSizeLog2(); + if ( (currentPos) && ( (currentPos) == (temp<>ClusterSizeLog2())+1; + clusterListLen = FAT().CountContiguousClustersL(aPos.iCluster, endCluster, maxClusters); + readLength = Min( aLength, (clusterListLen<Caps(caps); + if ( r != KErrNone ) + User::LeaveIfError(r); + if ( caps().iType&EMediaRam ) + { + realPosition = FAT().DataPositionInBytes( aPos.iCluster ); + aPos.iCluster = I64LOW((realPosition - aInfo.iStartBlockAddress)>>ClusterSizeLog2()); + blockMapEntry.SetStartBlock( aPos.iCluster ); + } + else + blockMapEntry.SetStartBlock( aPos.iCluster - 2); + aInfo.iMap.Append(TPckgC(blockMapEntry)); + aPos.iPos += readLength; + aPos.iCluster = endCluster; + aLength -= readLength; + } + while( ( aLength > 0 ) && ( ++i < KMaxMapsPerCall ) ); + } + + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::GetDosEntryFromNameL(const TDesC& aName, TEntryPos& aDosEntryPos, TFatDirEntry& aDosEntry) + { + TFatDirEntry firstEntry; + TEntryPos firstEntryPos(RootIndicator(),0); // Already checked entry is a directory + FindEntryStartL(aName,KEntryAttMaskSupported,firstEntry,firstEntryPos); + + aDosEntryPos=firstEntryPos; + aDosEntry=firstEntry; + MoveToDosEntryL(aDosEntryPos,aDosEntry); + + return KErrNone; + } + +//----------------------------------------------------------------------------------------- + +TInt CFatMountCB::GetFileUniqueId(const TDesC& aName, TInt64& aUniqueId) + { + // Get first cluster of file + TEntryPos dosEntryPos(RootIndicator(),0); + TFatDirEntry dosEntry; + InitializeRootEntry(dosEntry); // Nugatory initialisation to placate warnings + TRAPD(err,GetDosEntryFromNameL(aName,dosEntryPos,dosEntry)); + if(err!=KErrNone) + return err; + TInt startCluster=StartCluster(dosEntry); + // Empty files will return a cluster of zero + if(startCluster==0) + return KErrEof; + aUniqueId=MAKE_TINT64(0,startCluster); + return KErrNone; + } +//----------------------------------------------------------------------------------------- + + +TInt CFatMountCB::Spare3(TInt /*aVal*/, TAny* /*aPtr1*/, TAny* /*aPtr2*/) + { + return KErrNotSupported; + } + +TInt CFatMountCB::Spare2(TInt /*aVal*/, TAny* /*aPtr1*/, TAny* /*aPtr2*/) + { + return KErrNotSupported; + } + +TInt CFatMountCB::Spare1(TInt /*aVal*/, TAny* /*aPtr1*/, TAny* /*aPtr2*/) + { + return KErrNotSupported; + } + +//----------------------------------------------------------------------------------------- + +//-- maximal level of recursion for the CheckDisk. i.e. the max. number of folded directories to check. +const TInt KCheckDskMaxRecursionLevel = 50; + +// +// Walks a directory cluster list then checks all the entries. +// +void CFatMountCB::ChkDirL(RBitVector& aFatBitVec, TInt aDirCluster) + { + //__PRINT1(_L("### CFatMountCB::ChkDirL() level:%d"),iChkDiscRecLevel); + + //-- check if we have reached the recursion limit. on hardware the stack is very limited + //-- and its overflow will lead to crash. + if(iChkDiscRecLevel++ >= KCheckDskMaxRecursionLevel) + { + __PRINT1(_L("CFatMountCB::ChkDirL() max recursion level(%d) reached. Leaving!"),iChkDiscRecLevel); + User::Leave(KErrTooBig); + } + + if(/*Is32BitFat() &&*/aDirCluster != 0 && (aDirCluster == RootIndicator()))//the bit in comments maybe required + WalkClusterListL(aFatBitVec, RootIndicator()); + + TEntryPos entryPos(aDirCluster,0); + FOREVER + { + TFatDirEntry entry; + ReadDirEntryL(entryPos,entry); + MoveToDosEntryL(entryPos,entry); + if (entry.IsEndOfDirectory()) + break; + if (IsRootDir(entryPos)&&(StartOfRootDirInBytes()+entryPos.iPos==(RootDirEnd()-KSizeOfFatDirEntry))) + { + if(!entry.IsErased()) + ChkEntryL(aFatBitVec, entry); + break; // Allows maximum number of entries in root directory + } + MoveToNextEntryL(entryPos); + if (entry.IsParentDirectory() || entry.IsCurrentDirectory() || entry.IsErased()) + continue; + ChkEntryL(aFatBitVec, entry); + } + + iChkDiscRecLevel--; + } + +//----------------------------------------------------------------------------------------- + +// +// Check FAT is valid for anEntry +// +void CFatMountCB::ChkEntryL(RBitVector& aFatBitVec, const TFatDirEntry& anEntry) + { + TInt listLength=0; + + if ((anEntry.Attributes()&(KEntryAttDir)) || anEntry.Size()) + listLength=WalkClusterListL(aFatBitVec, StartCluster(anEntry)); + else if (anEntry.StartCluster() != 0) // zero length file + User::Leave(EFatChkDskInvalidEntrySize); // shouldn't have clusters + + if (anEntry.Attributes()&KEntryAttDir) + ChkDirL(aFatBitVec, StartCluster(anEntry)); + + // Check that the correct number of clusters have been allocated for the size of the file. + + else if ((anEntry.Attributes()&KEntryAttVolume)==0) + { + TInt clustersForFileSize; + TInt clusterSize=1<> ClusterSizeLog2() ); + + if (listLength!=clustersForFileSize) + User::Leave(EFatChkDskInvalidEntrySize); + } + } + +//----------------------------------------------------------------------------------------- + +// +// Walks cluster list from aCluster to EOF +// Reports an error if an invalid cluster is found before EOF or +// a cluster has been visited before. +// +TInt CFatMountCB::WalkClusterListL(RBitVector& aFatBitVec, TInt aCluster) + { + + TInt i=0; + do { + i++; + if (!ValidClusterNumber(aCluster)) + { + __PRINT1(_L("Bad Cluster number %d"),aCluster); + User::Leave(EFatChkDskIllegalClusterNumber); + } + + if (IsClusterVisited(aFatBitVec, aCluster)) + { + __PRINT1(_L("Cluster already in use %d"),aCluster); + User::Leave(EFatChkDskClusterAlreadyInUse); + } + + MarkClusterVisited(aFatBitVec, aCluster); + + } while (FAT().GetNextClusterL(aCluster)); + + return(i); + } + +//----------------------------------------------------------------------------------------- + +// +// Checks that all unvisited clusters are marked as free in the FAT +// +void CFatMountCB::CheckUnvisitedClustersL(const RBitVector& aFatBitVec) const + { + + TInt cluster=2; + TInt maxCluster=cluster + UsableClusters(); + while (cluster= maxCluster) + break; + + TInt clusterVal=FAT().ReadL(cluster); + if (clusterVal!=0 && IsEndOfClusterCh(clusterVal)==EFalse && !IsBadCluster(clusterVal)) + { + __PRINT1(_L("\n*****Bad cluster Num = %d"),cluster); + User::Leave(EFatChkDskBadCluster); + } + } + } + +//----------------------------------------------------------------------------------------- + +/** +@param aCluster cluster number to check for validity +@returns ETrue if aCluster is a valid cluster number +*/ +TBool CFatMountCB::ValidClusterNumber(TUint32 aCluster) const + { + return (aCluster>=KFatFirstSearchCluster && aCluster<=MaxClusterNumber()); + } + +//----------------------------------------------------------------------------------------- + +// +// Walk the FAT, returns error if find an unterminated list or +// lists that merge. +// +TInt CFatMountCB::CheckDisk() + { + + __PRINT1(_L("CFatMountCB::CheckDisk() drv:%d"), DriveNumber()); + + if(!ConsistentState()) + return KErrCorrupt; + + //-- create a bit representation of the FAT + const TUint32 MaxClusters = UsableClusters()+KFatFirstSearchCluster; //-- UsableClusters() doesn't count first 2 unused clusers + + + // cluster count may be zero if boot sector failed to be read (e.g. if the media is locked) + // or if TDrive::MountMedia(ETrue) has been called (in which case the boot sector may + if (MaxClusters == 0) + return KErrCorrupt; + + RBitVector bitVec; //-- each bit in this vector represents a FAT cluster + + TInt nRes = bitVec.Create(MaxClusters); + if(nRes != KErrNone) + { + ASSERT(nRes == KErrNoMemory); //-- the only one possible reason. + return KErrNoMemory; + } + + iChkDiscRecLevel = 0; //-- reset CheckDisk recursion counter + TRAPD(r,ChkDirL(bitVec, RootIndicator())); // Check from root directory + if (r==KErrNone) + { + TRAP(r,CheckUnvisitedClustersL(bitVec)); + } + + + bitVec.Close(); + + switch(r) + { + + case KErrNone: + return KErrNone; + + case EFatChkDskIllegalClusterNumber: + return(1); + + case EFatChkDskClusterAlreadyInUse: + return(2); + + case EFatChkDskBadCluster: + return(3); + + case EFatChkDskInvalidEntrySize: + return(4); + + default: + break; + } + + return(r); + } + + +//----------------------------------------------------------------------------------------- +// Helper functions for Check Disk functionality +//----------------------------------------------------------------------------------------- + +/** + Find the next unvisited cluster number in the bit array. + + @param aBitList bit array, where '0' bits represent unvisited clusters. + @param aCluster cluster number to start search with. + + @return positive integer indicating next unvisited cluster number + KErrNotFound (-1) if there are no unvisited clusters + +*/ +static TInt NextUnvisitedCluster(const RBitVector& aFatBitVec, TUint32 aCluster) +{ + __ASSERT_DEBUG(aCluster >= KFatFirstSearchCluster, Fault(EFatChkDskIllegalClusterNumber)); //-- 1st 2 FAT entries are reserved + + TUint32 searchPos = aCluster; //-- bit number to start search with + + //-- look for the unvisited cluster (bit '0') in the bit array from the searchPos to the right + if(aFatBitVec.Find(searchPos, 0, RBitVector::ERight)) + return searchPos; + + return KErrNotFound; +} + + +/** + Check if we have visited cluster aCluster + + @param aFatBitVec bit array, where '0' bits represent unvisited clusters. + @param aCluster cluster number to check + @return ETrue if aCluster has been visited. +*/ +static TBool IsClusterVisited(const RBitVector& aFatBitVec, TUint32 aCluster) +{ + __ASSERT_DEBUG(aCluster >= KFatFirstSearchCluster, Fault(EFatChkDskIllegalClusterNumber)); //-- 1st 2 FAT entries are reserved + + return aFatBitVec[aCluster]; +} + +/** + Mark aCluster as visited + @param aFatBitVec bit array, where '0' bits represent unvisited clusters. + @param aCluster cluster number to mark +*/ +static void MarkClusterVisited(RBitVector& aFatBitVec, TUint32 aCluster) +{ + __ASSERT_DEBUG(aCluster >= KFatFirstSearchCluster, Fault(EFatChkDskIllegalClusterNumber)); //-- 1st 2 FAT entries are reserved + + aFatBitVec.SetBit(aCluster); //-- '1' bit in a bit array means that the corresponding cluster is visited +} + + + +//------------------------------------------------------------------------------------------------------------------- + +/** + Creates a scan drive object and starts the scan. +*/ +TInt CFatMountCB::DoRunScanDrive() +{ + TInt nRes; + + CScanDrive* pScnDrv = NULL; + TRAP(nRes, pScnDrv=CScanDrive::NewL(this)); + if(nRes != KErrNone) + return nRes; + + TRAPD(nScnDrvRes, pScnDrv->StartL()); + + const TBool bNeedFatRemount = (nScnDrvRes!=KErrNone) || pScnDrv->ProblemsDiscovered(); + delete pScnDrv; + + + if(bNeedFatRemount) + {//-- ScanDrive found and probably fixed some errors. + // ensure cached fat and free cluster count are updated + DoDismount(); //-- dismount + TRAP(nRes, MountL(EFalse)); //-- mount again + } + + if(nScnDrvRes != KErrNone) + return nScnDrvRes; + + + //-- if ScanDrive hasn't found anything wrong or has fixed recoverable erros, mark the volume clean + if(VolCleanFlagSupported()) + { + //-- if there is a background FAT scanning thread, we need to wait until it finishes its work. + //-- otherwise it's possible to have incorrect amount of free space on the volume until next remounting. + (void)FAT().NumberOfFreeClusters(ETrue); + TRAP(nRes, FinaliseMountL()); + ASSERT(nRes == KErrNone); + } + + return KErrNone; +} + +//------------------------------------------------------------------------------------------------------------------- + +/** + Run scan drive on the given volume. + The ScanDrive may be skipped on the finalised volumes, i.e. those, that had been shut down properly. + + + @return Either KErrCorrupt if an error was found that is not caused by write failure due to power removal. + KErrNone if no error was found. One of four positive codes explaining what type of error was rectified +*/ +TInt CFatMountCB::ScanDrive() +{ + __PRINT1(_L("CFatMountCB::ScanDrive() starting on drive %d"), DriveNumber()); + + if(!ConsistentState()) + return KErrCorrupt; + + TInt nRes; + + if(LockStatus()!=0) + { + __PRINT(_L("CFatMountCB::ScanDrive() locked!\n")); + return KErrInUse; + } + + if(iRamDrive) + {//-- Do not check internal RAM drive + __PRINT(_L("CFatMountCB::ScanDrive() Skipping Internal RAM drive.")); + return KErrNone; + } + + //-- check if the volume is finalised and skip running ScanDrive if this option is enabled in estart.txt + if(VolCleanFlagSupported() && FatConfig().ScanDrvSkipFinalisedVolume()) + { + TBool bVolClean = EFalse; + TRAP(nRes, bVolClean = VolumeCleanL()); + + if(nRes == KErrNone && bVolClean) + { + __PRINT(_L("Skipping ScanDrive on finalised volume!")); + return KErrNone; //-- skip ScanDrive on a clean volume + } + } + + //-- run ScanDrive + nRes = Open(); + if(nRes != KErrNone) + return nRes; + + nRes = DoRunScanDrive(); + + Close(); + + __PRINT2(_L("~ CFatMountCB::ScanDrive() finished for drive %d with the code %d"),DriveNumber(), nRes); + + return nRes; + +} + +//----------------------------------------------------------------------------------------- +/** +Returns the offset between UTC time and timestamps on the filesystem. This will return User::UTCOffset +if the flag iUseLocalTime has been set in CFatFileSystem and this mount is on a removable drive. If not +a null offset is returned. + +@return The offset in seconds that timestamps on the filesystem have, relative to UTC. +*/ +TTimeIntervalSeconds CFatMountCB::TimeOffset() const + { + if((Drive().Att() & KDriveAttRemovable) && FatFileSystem().GetUseLocalTime() ) + { + return User::UTCOffset(); + } + else + { + return TTimeIntervalSeconds(0); + } + } + + + + +//----------------------------------------------------------------------------------------- +/** + Check is this file system can be mounted on the drive at all. + Just read and validate boot region, no real mounting overhead. + + @return KErrNone boot region is OK, the file system can be mounted. + KErrLocked the media is locked on a physical level. + other error codes depending on the implementation + +*/ +TInt CFatMountCB::MntCtl_DoCheckFileSystemMountable() + { + TInt nRes; + + const TInt driveNo = Drive().DriveNumber(); + __PRINT1(_L("CFatMountCB::MntCtl_DoCheckFileSystemMountable() drv:%d"),driveNo); + + nRes = CreateDrive(driveNo); + if(nRes != KErrNone) + { + __PRINT1(_L(" ..CreateDrive() err:%d \n"), nRes); + return nRes; + } + + //-- try reading boot sector. This doesn't require iDriverInterface setup, it uses LocalDrive() + TFatBootSector bootSector; + nRes = ReadBootSector(bootSector); + + DismountedLocalDrive(); + + return nRes; + } + + +