// Copyright (c) 1998-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:
// f32test\scndrv\t_tscan32.cpp
// 
//

#include <f32file.h>
#include <e32test.h>

#include "t_server.h"

#include "fat_utils.h"
using namespace Fat_Test_Utils;

/*
Series of tests to check that the combination of a ruggedised fat file
system and scandisk prevents the file system from becoming corrupt in the
event of a power failure. CheckDisk is used to test that directory
structure is not corrupt. This test is only suitable with a drive that uses
the fat file system but not the internal ram drive (due to the indirection)
table. Only works with debug builds due to RFs::ControlIo only debug function
*/

GLDEF_D TFileName StartupExeName=_L(""); // initialised at run time

#ifdef _DEBUG
GLREF_D RTest test;
GLDEF_D TInt TheFunctionNumber;
GLDEF_D TInt TheOpNumber;
GLDEF_D TInt TheFailCount;
GLDEF_D TBool IsReset;
GLDEF_D TFileName TestExeName=_L("?:\\T_SCANDR.EXE"); //Renaming it to fit in one root dir entry.
GLDEF_D TFileName LogFileName=_L("?:\\T_SCANDR.LOG"); //Renaming it to fit in one root dir entry.

const TInt KControlIoWriteFailOn=0;		// commands to pass into RFs::ControlIo
const TInt KControlIoWriteFailOff=1;
const TInt KMaxFatEntries  = 2048;
const TInt KDirAttrReadOnly  = 0x01;
const TInt KDirAttrHidden    = 0x02;
const TInt KDirAttrSystem    = 0x04;
const TInt KDirAttrVolumeId  = 0x08;
const TInt KDirAttrDirectory = 0x10;
const TInt KDirAttrArchive   = 0x20;
const TInt KDirAttrLongName  = KDirAttrReadOnly | KDirAttrHidden | KDirAttrSystem | KDirAttrVolumeId;
const TInt KDirAttrLongMask  = KDirAttrLongName | KDirAttrDirectory | KDirAttrArchive;
const TInt KDirLastLongEntry = 0x40;

GLDEF_D TInt WriteFailValue;

LOCAL_C TFatBootSector BootSector;	
LOCAL_D RRawDisk TheRawDisk;

static TFatType gDiskType = EInvalid;

LOCAL_D TInt gTotalSectors;
LOCAL_D TInt gBytesPerCluster;
LOCAL_D TInt gRootDirSectors;
LOCAL_D TInt gRootDirEntries;
LOCAL_D TInt gRootDirStart;
LOCAL_D TInt gRootSector;
LOCAL_D TInt gFatStartBytes;
LOCAL_D TInt gFatTestEntries;
LOCAL_D TInt gFatSizeSectors;
LOCAL_D TInt gFirstDataSector;
LOCAL_D TInt gDataStartBytes;
LOCAL_D TInt gClusterCount;

LOCAL_D HBufC8* gFatBuf  = NULL;
LOCAL_D TInt    gFatAddr = -1;

enum TFatChain {EChainStd,EChainAlternate,EChainBackwards,EChainForwards};

LOCAL_C TBool IsInternalRam()
//
// Returns true if the selected drive is variable size (i.e. RAM drive)
//
	{
	TVolumeInfo v;
	TInt r=TheFs.Volume(v,gSessionPath[0]-'A');
	test(r==KErrNone);
	return(v.iDrive.iMediaAtt&KMediaAttVariableSize);
	}

LOCAL_C void WriteLogFile()
//
// Writes state of test to end of LogFileName
//
	{
	test.Printf(_L("Writelogfile()\n"));
	RFile log;
	TInt r=log.Open(TheFs,LogFileName,EFileShareExclusive|EFileWrite);
	if(r!=KErrNone)
		test.Printf(_L("error=%d\n"),r);
	test(r==KErrNone);
	TInt size;
	r=log.Size(size);
	test(r==KErrNone);
	TBuf8<16> buf;
	buf.SetLength(4);
	buf[0]=(TUint8)TheFunctionNumber;
	buf[1]=(TUint8)TheOpNumber;
	buf[2]=(TUint8)TheFailCount;
	buf[3]='\n';
	r=log.Write(size,buf,buf.Length());
	test(r==KErrNone);
	test.Printf(_L("Written func=%d,op=%d,fail=%d\n"),TheFunctionNumber,TheOpNumber,TheFailCount);
	log.Close();
	}

LOCAL_C TInt SetWriteFailOn(TInt aFailCount)
//
// Sets write to metadata to fail on aFailCount with WriteFailValue
//
	{
	TInt16 args[2];
	TPtr8 des((TUint8*)args,4,4);
	args[0]=(TUint16)aFailCount;
	args[1]=(TUint16)WriteFailValue;
	TInt r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOn,des);
	return(r);
	}

GLDEF_C void ReadLogFile()
//
// Reads state of test from end of LogFileName and sets global variables
//
	{
	test.Next(_L("ReadLogFile"));
	RFile log;
	TInt r=log.Open(TheFs,LogFileName,EFileShareExclusive);
	if(r!=KErrNone)
		test.Printf(_L("error in ReadLogFile()=%d\n"),r);
	test(r==KErrNone);
	test(r==KErrNone);
	TInt fileSize;
	r=log.Size(fileSize);
	if(fileSize==0)
		{
		TheFunctionNumber=0;
		TheOpNumber=0;
		TheFailCount=0;
		}
	else
		{
		TBuf8<4> buf;
		r=log.Read(fileSize-4,buf,4);
		TheFunctionNumber=buf[0];
		TheOpNumber=buf[1];
		TheFailCount=buf[2];
		}
	log.Close();
	test.Printf(_L("func=%d,op=%d,fail=%d\n"),TheFunctionNumber,TheOpNumber,TheFailCount);
	}

LOCAL_C TUint32 MaxClusters()
	//
	// Return the number of data clusters on the disk
	//
	{
	TUint32 totSec = (BootSector.TotalSectors() ? BootSector.TotalSectors() : BootSector.HugeSectors());
	TUint32 numSec = totSec - gFirstDataSector;
	return numSec / BootSector.SectorsPerCluster();
	}

LOCAL_C TInt ClusterToByte(TInt aCluster)
	//
	// converts cluster number to byte offset on disk
	//
	{
	TInt sector = (aCluster - 2) * gBytesPerCluster + gFirstDataSector * BootSector.BytesPerSector();
	return sector;
	}

/**
    Fill media with zeroes from aStartPos to aEndPos
*/
static void DoZeroFillMedia(TInt64 aStartPos, TInt64 aEndPos, RRawDisk& aWriter)
{
    test(aStartPos >=0 && aEndPos >=0 && aStartPos < aEndPos);
    
    if(aStartPos == aEndPos)
        return;

    RBuf8 buf;
    TInt  nRes;  

    const TUint32 KBufSz=65536*2; //-- buffer with zeroes
    
    nRes = buf.CreateMax(KBufSz);
    test(nRes == KErrNone);

    buf.FillZ();

    TUint32 rem = (TUint32)(aEndPos - aStartPos);
    while(rem)
    {
        const TUint32 bytesToWrite=Min(rem, KBufSz);
    
        TPtrC8 ptr(buf.Ptr(), bytesToWrite);
        nRes = aWriter.Write(aStartPos, ptr);
        test(nRes == KErrNone || nRes == KErrDiskFull);

        aStartPos+=bytesToWrite;
        rem-=bytesToWrite;
    }


    buf.Close();
}

//
// Clear the disk data area to a known value which won't be confused with
// directory entries etc.
//
LOCAL_C void ClearDiskData()
	{

	TInt r=TheRawDisk.Open(TheFs,gSessionPath[0]-'A');
	test(r==KErrNone);

	TUint32 startPos = gDataStartBytes;
	if (gDiskType == EFat32)
		startPos += gBytesPerCluster;

    const TUint32 endPos = startPos + gFatTestEntries*gBytesPerCluster;

    test.Printf(_L("ClearDiskData() from pos:%u to pos:%u\n"), startPos, endPos);

    DoZeroFillMedia(startPos, endPos, TheRawDisk);

	TheRawDisk.Close();
	}

LOCAL_C TInt PosInBytes(TInt aFatIndex)
//
// Return number of bytes into the FAT
//
	{
	TInt fatPosInBytes = -1;
	switch (gDiskType)
		{
		case EFat32:
			fatPosInBytes=aFatIndex<<2;
			break;
		case EFat16:
			fatPosInBytes=aFatIndex<<1;
			break;
		case EFat12:
			fatPosInBytes=(aFatIndex*3>>1);
			break;
		default:
			test(0);
		}
	return(fatPosInBytes);
	}

static void DoReadBootSector()
	{
	
    TInt nRes = ReadBootSector(TheFs, CurrentDrive(), KBootSectorNum<<KDefaultSectorLog2, BootSector);
    test(nRes == KErrNone);

    if(!BootSector.IsValid())
        {
        test.Printf(_L("Wrong bootsector! Dump:\n"));
        BootSector.PrintDebugInfo();
        test(0);
        }
	// Calculate derived variables (fixed for a particular disk format)
	gBytesPerCluster   = BootSector.BytesPerSector() * BootSector.SectorsPerCluster();
	gRootDirSectors    = ((BootSector.RootDirEntries() * KSizeOfFatDirEntry + BootSector.BytesPerSector() - 1) / BootSector.BytesPerSector());
	gFatStartBytes     = BootSector.ReservedSectors() * BootSector.BytesPerSector();
	gFatSizeSectors    = (BootSector.FatSectors() ? BootSector.FatSectors() : BootSector.FatSectors32());
	gRootSector        = BootSector.ReservedSectors() + BootSector.NumberOfFats() * gFatSizeSectors;
	gRootDirStart      = gRootSector * BootSector.BytesPerSector();
	gFirstDataSector   = gRootSector + gRootDirSectors;

	gFatTestEntries = MaxClusters();
	if (gFatTestEntries > KMaxFatEntries)
		gFatTestEntries = KMaxFatEntries;

	if (BootSector.RootDirEntries() == 0)
		{
		test.Printf(_L("**** Is Fat32\n"));
		gDiskType = EFat32;
		gRootDirEntries = BootSector.RootDirEntries();
		}
	else if (BootSector.FatType() == EFat16)
		{
		test.Printf(_L("**** Is Fat16\n"));
		gDiskType = EFat16;
		gRootDirEntries = BootSector.RootDirEntries();
		}
	else
		{
		test.Printf(_L("**** Is Fat12\n"));
		gDiskType = EFat12;
		gRootDirEntries = gBytesPerCluster * 1 / KSizeOfFatDirEntry;
		}
	gTotalSectors   = (BootSector.TotalSectors() ? BootSector.TotalSectors() : BootSector.HugeSectors());
	gClusterCount   = (gTotalSectors - gFirstDataSector) / BootSector.SectorsPerCluster();
	gDataStartBytes = gFirstDataSector * BootSector.BytesPerSector();
	}

GLDEF_C TUint32 GetFatEntry(TUint32 aIndex, const TUint8* aFat=NULL)
//
// Read a single FAT entry from disk or FAT copy and return it
//
	{


	if (!gFatBuf)
		{
		gFatBuf=HBufC8::New(gBytesPerCluster);
		test(gFatBuf!=NULL);
		gFatAddr = -1;
		}

	const TUint8* ptr;

	if (aFat)
		{
		ptr = (TUint8*)aFat + PosInBytes(aIndex);
		}
	else
		{
		TInt pos = PosInBytes(aIndex) + gFatStartBytes;
		if (gFatAddr < 0 || pos < gFatAddr || pos >= gFatAddr + gBytesPerCluster)
			{
			TPtr8 ptr=gFatBuf->Des();
		TInt r=TheRawDisk.Open(TheFs,gSessionPath[0]-'A');
			test(r==KErrNone);
			r=TheRawDisk.Read(pos, ptr);
			test(r==KErrNone);
			TheRawDisk.Close();
			gFatAddr = pos;
			}
		ptr = gFatBuf->Ptr() + pos-gFatAddr;
		}

	TUint32 val = 0;
	switch (gDiskType)
		{
		case EFat32:
			val = *(TUint32*)ptr;
			break;
		case EFat16:
			val = *(TUint16*)ptr;
			break;
		case EFat12:
			val = *(TUint16*)ptr;
			if (aIndex & 1)
				val >>= 4;
			val &= 0xFFF;
			break;
		default:
			test(0);
		}
	return val;
	}

GLDEF_C void DumpBootSector()
//
// Display (in log) TFatBootSector structure
//
	{
	RDebug::Print(_L("iBytesPerSector    = %8d"), BootSector.BytesPerSector());
	RDebug::Print(_L("iSectorsPerCluster = %8d"), BootSector.SectorsPerCluster());
	RDebug::Print(_L("iReservedSectors   = %8d"), BootSector.ReservedSectors());
	RDebug::Print(_L("iNumberOfFats      = %8d"), BootSector.NumberOfFats());
	RDebug::Print(_L("iRootDirEntries    = %8d"), BootSector.RootDirEntries());
	RDebug::Print(_L("iTotalSectors      = %8d"), BootSector.TotalSectors());
	RDebug::Print(_L("iMediaDescriptor   = %8d"), BootSector.MediaDescriptor());
	RDebug::Print(_L("iFatSectors        = %8d"), BootSector.FatSectors());
	RDebug::Print(_L("iSectorsPerTrack   = %8d"), BootSector.SectorsPerTrack());
	RDebug::Print(_L("iNumberOfHeads     = %8d"), BootSector.NumberOfHeads());
	RDebug::Print(_L("iHiddenSectors     = %8d"), BootSector.HiddenSectors());
	RDebug::Print(_L("iHugeSectors       = %8d"), BootSector.HugeSectors());

	//New for FAT32

	if(BootSector.RootDirEntries() == 0)	//indicates we have FAT32 volume
		{
		RDebug::Print(_L("FatSectors32      = %8d"), BootSector.FatSectors32());
		RDebug::Print(_L("FATFlags          = %8d"), BootSector.FATFlags());
		RDebug::Print(_L("VersionNumber     = %8d"), BootSector.VersionNumber());
		RDebug::Print(_L("RootClusterNum    = %8d (0x%08X)"), BootSector.RootClusterNum(), gRootDirStart);
		RDebug::Print(_L("FSInfoSectorNum   = %8d (0x%08X)"), BootSector.FSInfoSectorNum(), BootSector.FSInfoSectorNum() * BootSector.BytesPerSector());
		RDebug::Print(_L("BkBootRecSector   = %8d (0x%08X)"), BootSector.BkBootRecSector(), BootSector.BkBootRecSector() * BootSector.BytesPerSector());
		}
	TInt fatEntries = gFatSizeSectors*BootSector.BytesPerSector();
	switch (gDiskType)
		{
		case EFat32:
			fatEntries /= 4;
			break;
		case EFat16:
			fatEntries /= 2;
			break;
		case EFat12:
			fatEntries *= 3;
			fatEntries /= 2;
			break;
		default:
			test(0);
		}
	RDebug::Print(_L("ClusterCount      = %8d (%d bytes)"), gClusterCount, gClusterCount*gBytesPerCluster);
	RDebug::Print(_L("FatEntries        = %8d (%d sectors)"), fatEntries, gFatSizeSectors);
	RDebug::Print(_L("RootSector        = %8d (0x%08X)"), gRootSector, gRootDirStart);
	RDebug::Print(_L("FirstDataSector   = %8d (0x%08X)"), gFirstDataSector, gDataStartBytes);
	}

GLDEF_C void DumpFat(const TUint8* aFat=NULL)
// 
// Dump to the log all those FAT entries which are non-zero
//
	{
	TInt32 max = MaxClusters();
	if (max > KMaxFatEntries)
		max = KMaxFatEntries;
	RDebug::Print(_L("---------------- DUMP OF FAT ---------------"));
	for (TInt32 i = 0; i < max; i++)
		{
		TInt32 val = GetFatEntry(i, aFat);
		TInt32 msk = 0x0FFFFFFF;
		switch (gDiskType)
			{
			case EFat32:
				msk = 0x0FFFFFFF;
				break;
			case EFat16:
				msk = 0xFFFF;
				break;
			case EFat12:
				msk = 0x0FFF;
				break;
			default:
				test(0);
			}
		if ((val & msk) == (0x0FFFFFFF & msk))
			RDebug::Print(_L("    %8d -> EOC"), i);
		else if ((val & msk) == (0x0FFFFFF8 & msk))
			RDebug::Print(_L("    %8d -> Media"), i);
		else if ((val & msk) == (0x0FFFFFF7 & msk))
			RDebug::Print(_L("    %8d -> BAD"), i);
		else if (val > max)
			RDebug::Print(_L("    %8d -> 0x%08X"), i, val);
		else if (val != 0)
			RDebug::Print(_L("    %8d -> %d"), i, val);
		}
	RDebug::Print(_L("--------------------------------------------"));
	}

GLDEF_C TDes* DirAttributes(TInt aAttrib)
// 
// Return a pointer to a local buffer containing the attribute letters.
//
	{
	LOCAL_D TBuf<6> str(_L("------"));
	LOCAL_D char*   atr = "RHSVDA";
	for (TInt i = 0; i < 6; i++)
		if ((aAttrib >> i) & 1)
			str[i] = atr[i];
	return &str;
	}

GLDEF_C TBool IsValidDirChar(TUint8 aChar, TUint8 aMin=0x20)
//
// Test whether a character is valid as part of a short filename, aMin is to
// distinguish between first character (which can't be space) and later ones
// which can include space but nothing less.  Note that E5 is a valid character
// in any position, even though it means 'erased' in the first character.
//
	{
	const TUint8* inval = (TUint8*)"\x22\x2A\x2B\x2C\x2E\x2F\x3A\x3B\x3C\x3D\x3E\x3F\x5B\x5C\x5D\x7C";
	if (aChar < aMin)
		return EFalse;
	for (const TUint8* p = inval; *p; p++)
		if (aChar == *p)
			return EFalse;
	return ETrue;
	}

GLDEF_C TBool IsValidDirEntry(TFatDirEntry* aDir)
// 
// Test whether buffer is a valid normal directory entry
//
	{
	// top two bits of attributes must be zero
	if (aDir->iData[11] & 0xC0)
		return EFalse;
	// first character must be 0x05 or greater than space
	if (!IsValidDirChar(aDir->iData[0], 0x21) && aDir->iData[0] != 0x05)
		return EFalse;
	// other characters in name must be not less than space
	for (TInt i = 1; i < 11; i++)
		if (!IsValidDirChar(aDir->iData[i]))
			return EFalse;
	return ETrue;
	}

GLDEF_C void GetLongNamePart(TDes16& aName, const TUint8* aEntry, TInt aPos, TInt aOffset, TInt aLength)
// 
// Extract part of a long name entry into the name buffer.
//
// @param aName   buffer to put name
// @param aEntry  directory entry raw data
// @param aPos    character in buffer to start name segment
// @param aOffset offset in directory entry of the segment
// @param aLength number of characters in the segment
// 
	{
	for (TInt i = 0; i < aLength; i++)
		{
		TInt at = i * 2 + aOffset;
		TInt ch = aEntry[at] + aEntry[at+1] * 256;
		aName[aPos++] = TText(ch);
		}
	}

GLDEF_C void ExtractNameString(TDes16& aName, const TUint8* aEntry)
//
// Extract a long name part from a directory entry, truncate it at the first
// NUL (0) character and put quotes round it.
//
	{
	aName.SetLength(15);
	TInt len = aName.Length() - 1;
	TText qu = '\'';
	aName[0] = qu;
	GetLongNamePart(aName, aEntry,  1,  1, 5);
	GetLongNamePart(aName, aEntry,  6, 14, 6);
	GetLongNamePart(aName, aEntry, 12, 28, 2);
	TInt i;
	for (i = 0; i < len; i++)
		if (aName[i] == 0)
			break;
	aName[i++] = qu;
	aName.SetLength(i);
	}

TDes& MakePrintable(TDes& aDes)
	{
	TInt len = aDes.Length();

	for (TInt i=0; i<len; i++)
		{
		if ((TUint8) aDes[i] < 0x20)
			aDes[i] = '?';
		}
	return aDes;
	}

GLDEF_C TBool DumpDirEntry(TInt aNum, const TUint8* aEntry)
// 
// Dump a single directory entry to the log.  Return false if it was end of
// directory or an invalid entry (and don't display it).
// 
	{
	TFatDirEntry* d = (TFatDirEntry*)aEntry;
	if (d->IsErased())
		{
		// RDebug::Print(_L("%5d: ERASED"), aNum);
		}
	else if (d->IsEndOfDirectory())
		return EFalse;
	else if (((TInt)d->Attributes() & KDirAttrLongMask) == KDirAttrLongName)
		{
		TBuf16<15> name;
		ExtractNameString(name, aEntry);
		TInt ord = aEntry[0];
		if (ord & KDirLastLongEntry)
			RDebug::Print(_L("%5d: %-15S #%-2d LAST"), aNum, &MakePrintable(name), ord & ~KDirLastLongEntry);
		else
			RDebug::Print(_L("%5d: %-15S #%-2d"), aNum, &MakePrintable(name), ord & ~KDirLastLongEntry);
		}
	else if (!IsValidDirEntry(d))
		return EFalse;
	else
		{
		TBuf<11> name;
		name.Copy(d->Name());
		RDebug::Print(_L("%5d: '%S'  %S  cluster %d"),
					  aNum, &MakePrintable(name), DirAttributes(d->Attributes()), d->StartCluster());
		}
	return ETrue;
	}

GLDEF_C void DumpDirCluster(const TUint8* aData, TInt aCluster=0)
//
// Dump directory entries until end of cluster or invalid/end entry found.
//
	{
	if (aCluster > 2)
		aData += (aCluster-2) * gBytesPerCluster;
	for (TInt i = 0; i < gBytesPerCluster; i += KSizeOfFatDirEntry)
		{
		if (DumpDirEntry(i/KSizeOfFatDirEntry, aData))
			aData += KSizeOfFatDirEntry;
		else
			break;
		}
	}

GLDEF_C void DumpData(const TUint8* aFat, TInt aStart, TInt aEnd)
//
// Dump clusters from disk (allows dumping of clusters not in our buffers).
// Only look at clusters marked as 'used' in the FAT.  Note that if aFat is
// NULL the FAT entries will also be read from disk (slower but allows for ones
// outside our copy in memory).
//
	{
	if (aStart > gFatTestEntries)
		return;
	RDebug::Print(_L("--------------- DATA AREA ------------------"));
	if (aEnd > gFatTestEntries)
		aEnd = gFatTestEntries;
	for (TInt cluster = aStart; cluster < aEnd; cluster++)
		{
		if (GetFatEntry(cluster, aFat) != 0)
			{
			HBufC8* buf=HBufC8::New(gBytesPerCluster);
			test(buf!=NULL);
			TPtr8 ptr=buf->Des();
			TInt r=TheRawDisk.Open(TheFs,gSessionPath[0]-'A');
			test(r==KErrNone);
			r=TheRawDisk.Read(ClusterToByte(cluster), ptr);
			test(r==KErrNone);
			TheRawDisk.Close();
			RDebug::Print(_L("Cluster %d @ 0x%08X:"), cluster, ClusterToByte(cluster));
			DumpDirCluster(ptr.Ptr());
			delete buf;
			}
		}
	RDebug::Print(_L("--------------------------------------------"));
	}

GLDEF_C void DumpHex(const TUint8* aData, TInt aLen)
//
// Dump a block of memory to the log in hex.
//
	{
	for (TInt base = 0; base < aLen; base += 16)
		{
		TBuf<16*3> buf;
		TInt off;
		for (off = base; off < aLen && off < base + 16; off++)
			{
			buf.Append(TText(' '));
			buf.AppendNumFixedWidth(aData[off], EHex, 2);
			}
		RDebug::Print(_L("%04X: %S"), off, &buf);
		}
	}

static void QuickFormat()
    {
        /*
        TFatFormatParam fmt;
        fmt.iFatType = EFat32;
        fmt.iSecPerCluster =1;
        FormatFatDrive(TheFs, CurrentDrive(), ETrue, &fmt);
        */
        FormatFatDrive(TheFs, CurrentDrive(), ETrue);
    }


LOCAL_C void MakeVeryLongName(TFileName& aLong)
//
// appends a very long file name to aLong
//
	{
	// create a name to take up 18 vfat entries - (1 sector + 2 entries)
	for(TInt i=0;i<234;++i)
		{
		TInt c='a'+i%26;
		aLong.Append(c);
		}
	}

LOCAL_C void MakeEntryName(TFileName& aName,TInt aLength)
//
// Appends aLength characters to aName
//
	{
	for(TInt i=0;i<aLength;++i)
		{
		TInt c='A'+i%26;
		aName.Append(c);
		}
	}

LOCAL_C void FillUpRootDir(TInt aFree=0)
//
// Fill up root directory
//
	{
	TInt maxRootEntries = gRootDirEntries -aFree;
	TFileName dir=_L("\\??\\");
	TInt count=0;
	TInt entriesSoFar;
	if(IsReset)
		entriesSoFar=2+2+2+2; // \\t_scn32dr3.exe + \\sys + \\t_scn32dr3.log + \\f32-tst
	else
		entriesSoFar=0;
	TInt r;
	while(entriesSoFar<maxRootEntries)
		{
		dir[1]=TUint16(count/26+'a');
		dir[2]=TUint16(count%26+'a');
		r=TheFs.MkDir(dir);
		test(r==KErrNone);
		entriesSoFar+=2;
		++count;
		}
	}

LOCAL_C void UnFillUpRootDir(TInt aFree=0)
//
// Reverse changes from FillUpRootDir()
//
	{
	TFileName dir=_L("\\??\\");
	TInt entriesSoFar=gRootDirEntries -aFree;
	TInt count=0;
	TInt r;
	TInt existing;
	if(IsReset)
		existing=2+2+2+2; // \\t_scn32dr3.exe + \\sys + \\t_scn32dr3.log + \\f32-tst
	else
		existing=0;
	while(entriesSoFar>existing)
		{
		dir[1]=TUint16(count/26+'a');
		dir[2]=TUint16(count%26+'a');
		r=TheFs.RmDir(dir);
		test(r==KErrNone);
		entriesSoFar-=2;
		++count;
		}
	}

void InitialiseWriteBuffer(TDes8& buf)
//
//
//
	{
	for(TInt i=0;i<buf.Length();++i)
		buf[i]=(TUint8)('a'+i%26);
	}

LOCAL_C TBool EntryExists(const TDesC& aName)	
//
// Returns ETrue if aName is found
//
	{
	TEntry entry;
	TInt r=TheFs.Entry(aName,entry);
	test(r==KErrNone||r==KErrNotFound);
	return(r==KErrNone?(TBool)ETrue:(TBool)EFalse);
	}

LOCAL_C TInt EntriesPerFatSector()
//
// Returns number of entries in one fat table sector
//
	{
	switch (gDiskType)
		{
		case EFat32:
			return(BootSector.BytesPerSector()/4);
		case EFat16:
			return(BootSector.BytesPerSector()/2);
		case EFat12:
			return(BootSector.BytesPerSector()*2/3);
		default:
			test(0);
		}
	return -1;
	}

LOCAL_C TBool OneEntryExists(const TDesC& aOldName,const TDesC& aNewName)
//
// Returns ETrue if only one of two entries exists
//
	{
	TBool oldExists=EntryExists(aOldName);
	TBool newExists=EntryExists(aNewName);
	return((!oldExists&&newExists)||(oldExists&&!newExists));
	}

LOCAL_C void GetEntryDetails(const TDesC& aName,TEntry& aEntry)
//
// returns entry details for the entry with aName
//
	{
	TInt r=TheFs.Entry(aName,aEntry);
	test(r==KErrNone);
	}

LOCAL_C TBool IsSameEntryDetails(TEntry aOldEntry,TEntry aNewEntry)
//
// 
//
	{
	return(aOldEntry.iAtt==aNewEntry.iAtt&&aOldEntry.iSize==aNewEntry.iSize&&aOldEntry.iModified==aNewEntry.iModified);
	}

LOCAL_C void CreateAlternate(const TDesC& aNameOne,const TDesC& aNameTwo)
//
// Creates altenate entries which take up one sector of fat table.
// By subsequently deleting  one of these entries a new entry can be made 
// with cluster chain that is not contiguous.
//
	{
	TInt entries=EntriesPerFatSector();
	RFile file1,file2;
	TInt size1,size2;
	size1=size2=0;
	TInt r=file1.Create(TheFs,aNameOne,EFileShareAny);
	test(r==KErrNone);
	r=file2.Create(TheFs,aNameTwo,EFileShareAny);
	test(r==KErrNone);
	// one entry for file1 for every 40 entries for file2
	// if file 1 subseqently deleted then 7 entries available
	// in that fat sector - ~3.5kb file size - for fat16
	TInt ratio=40;
	TBool first=ETrue;
	while(entries>0)
		{
		if(first)
			{
			size1+=gBytesPerCluster;
			r=file1.SetSize(size1);
			test(r==KErrNone);
			first=EFalse;
			--entries;
			}
		else
			{
			size2+=gBytesPerCluster*ratio;
			r=file1.SetSize(size1);
			test(r==KErrNone);
			first=ETrue;
			entries-=ratio;
			}
		}
	file1.Close();
	file2.Close();
	}

LOCAL_C TInt ThrottleDirEntries(TInt aDirEntries)
	{
	// throttle the number of entries needed, since for large cluster
	// sizes, this can take forever (eg 2GB card -> a cluster size of 32K
	// -> 1024 entries per cluster
	const TInt KMaxDirEntries = 2048;
	if (aDirEntries > KMaxDirEntries)
		{
		RDebug::Print(_L("Reducing directory entries from %d to %d"), 
			aDirEntries, KMaxDirEntries);
		aDirEntries = KMaxDirEntries;
		}
	return aDirEntries;
	}


LOCAL_C void CleanDirectory(const TDesC& aName,TInt aClusters)
//
// Removes entries in the directory
//
	{
	if (aClusters < 1)
		return;
	TInt entriesPerCluster=gBytesPerCluster/32;
	TInt entriesNeeded = entriesPerCluster * aClusters;

	entriesNeeded = ThrottleDirEntries(entriesNeeded);

	TInt maxFileNameLen = 250 - aName.Length();
	TInt nameEntries = 1 + (maxFileNameLen+12) / 13;
	TInt namesNeeded = (entriesNeeded + nameEntries-1) / nameEntries;
	TInt entry = 0;
	for(TInt i = 0; i < namesNeeded; ++i)
		{
		if (entriesNeeded - entry < nameEntries)
			maxFileNameLen = (entriesNeeded - entry - 1) * 13;
		TFileName fn;
		fn.AppendNum(entry);
		fn.Append('_');
		while (fn.Length() < maxFileNameLen)
			fn.Append('0');
		TFileName fullName(aName);
		fullName.Append(fn);
		TInt r = TheFs.Delete(fullName);
		test(r == KErrNone);
		entry += 1 + (fn.Length() + 12) / 13;
		}
	RDebug::Print(_L("CleanDirectory(%S, %d)"), &aName, aClusters);
	}

LOCAL_C void ExpandDirectory(const TDesC& aName,TInt aClusters)
//
// Expands the directory by aClusters
//
	{
	if (aClusters < 1)
		return;
	TInt entriesPerCluster=gBytesPerCluster/32;
	TInt entriesNeeded = entriesPerCluster * aClusters;

	entriesNeeded = ThrottleDirEntries(entriesNeeded);

	TInt maxFileNameLen = 250 - aName.Length();
	TInt nameEntries = 1 + (maxFileNameLen+12) / 13;
	TInt namesNeeded = (entriesNeeded + nameEntries-1) / nameEntries;
	TInt entry = 0;
	for(TInt i = 0; i < namesNeeded; ++i)
		{
		if (entriesNeeded - entry < nameEntries)
			maxFileNameLen = (entriesNeeded - entry - 1) * 13;
		TFileName fn;
		fn.AppendNum(entry);
		fn.Append('_');
		while (fn.Length() < maxFileNameLen)
			fn.Append('0');
		TFileName fullName(aName);
		fullName.Append(fn);
		RFile file;
		TInt r = file.Create(TheFs,fullName,EFileShareAny);
		test(r == KErrNone);
		file.Close();
		entry += 1 + (fn.Length() + 12) / 13;
		}
	// to leave a directory expanded by aClusters but with no additional entries
	RDebug::Print(_L("ExpandDirectory(%S, %d)"), &aName, aClusters);
	CleanDirectory(aName,aClusters);
	}

LOCAL_C TInt DeleteAlternateEntry(const TDesC& aName,TBool aIsDir)
//
// Deletes entry aName and corresponding entries created for EChainAlternate
//
	{
	TInt r=TheFs.Delete(_L("\\fat\\file2"));
	test(r==KErrNone||KErrNotFound);
	if(aIsDir)
		return(TheFs.RmDir(aName));
	else
		return(TheFs.Delete(aName));
	}

LOCAL_C TInt CreateAlternateEntry(const TDesC& aName,TBool aIsDir,TInt aSize)
//
// Creates entry with aName where cluster chain grows forward but not contiguously.
// Assumes that no holes in fat clusters.
//
	{
	TInt r=DeleteAlternateEntry(aName,aIsDir);
	test(r==KErrNone||r==KErrNotFound);
	RFile file;
	if(aIsDir)
		{
		r=TheFs.MkDir(aName);
		if(r!=KErrNone)
			return(r);
		}
	else
		{
		r=file.Create(TheFs,aName,EFileShareAny);
		if(r!=KErrNone)
			return(r);
		r=file.SetSize(1); //ensure file allocated a start cluster
		test(r==KErrNone);
		}
	CreateAlternate(_L("\\fat\\file1"),_L("\\fat\\file2"));
	r=TheFs.Delete(_L("\\fat\\file1"));
	test(r==KErrNone);
	if(aIsDir)
		ExpandDirectory(aName,aSize);
	else
		{
		r=file.SetSize(aSize);
		test(r==KErrNone);
		file.Close();
		}
	return(KErrNone);
	}

LOCAL_C TInt DeleteForwardEntry(const TDesC& aName,TBool aIsDir)
//
// Deletes entry with aName and corresponding entries created for EChainForward
//
	{
	TInt r=TheFs.Delete(_L("\\fat\\file2"));
	test(r==KErrNone||r==KErrNotFound);
	r=TheFs.Delete(_L("\\fat\\file4"));
	test(r==KErrNone||r==KErrNotFound);
	r=TheFs.Delete(_L("\\fat\\file5"));
	test(r==KErrNone||r==KErrNotFound);
	if(aIsDir)
		r=TheFs.RmDir(aName);
	else
		r=TheFs.Delete(aName);
	return r;
	}
	
LOCAL_C TInt CreateForwardEntry(const TDesC& aName,TBool aIsDir,TInt aSize)
//
// Creates an entry whose cluster chain first goes forward (upto 3.5kb for fat16 file)
// and then backwards
//	
	{
	TInt r=DeleteForwardEntry(aName,aIsDir);
	test(r==KErrNone||r==KErrNotFound);
	RFile file1,file2,entry;
	r=file1.Create(TheFs,_L("\\fat\\file1"),EFileShareAny);
	test(r==KErrNone);
	r=file1.SetSize(EntriesPerFatSector()*gBytesPerCluster);
	test(r==KErrNone);
	r=file2.Create(TheFs,_L("\\fat\\file2"),EFileShareAny);
	test(r==KErrNone);
	r=file2.SetSize(EntriesPerFatSector()*gBytesPerCluster);
	test(r==KErrNone);
	if(aIsDir)
		{
		r=TheFs.MkDir(aName);
		if(r!=KErrNone)
			return(r);
		}
	else
		{
		r=entry.Create(TheFs,aName,EFileShareAny);
		if(r!=KErrNone)
			return(r);
		r=entry.SetSize(1);	// ensure entry has start cluster allocated
		test(r==KErrNone);
		}
	CreateAlternate(_L("\\fat\\file3"),_L("\\fat\\file4"));
	RFile file5;
	r=file5.Create(TheFs,_L("\\fat\\file5"),EFileShareAny);
	test(r==KErrNone);
	r=file5.SetSize(EntriesPerFatSector()*gBytesPerCluster*2);
	test(r==KErrNone);
	file1.Close();
	file2.Close();
	file5.Close();
	r=TheFs.Delete(_L("\\fat\\file1"));
	test(r==KErrNone);
	r=TheFs.Delete(_L("\\fat\\file3"));
	test(r==KErrNone);
	if(aIsDir)
		ExpandDirectory(aName,aSize);
	else
		{
		r=entry.SetSize(aSize);
		test(r==KErrNone);
		entry.Close();
		}
	return(KErrNone);
	}

LOCAL_C TInt DeleteBackwardEntry(const TDesC& aName,TBool aIsDir)
//
// Deletes entry with aName and corresponding entries created for EChainBackwards
//
	{
	TInt r=TheFs.Delete(_L("\\fat\\file2"));
	test(r==KErrNone||r==KErrNotFound);
	r=TheFs.Delete(_L("\\fat\\file3"));
	test(r==KErrNone||r==KErrNotFound);
	if(aIsDir)
		r=TheFs.RmDir(aName);
	else
		r=TheFs.Delete(aName);
	return r;
	}

LOCAL_C TInt CreateBackwardEntry(const TDesC& aName,TBool aIsDir,TInt aSize)
//
// Creates an entry whose fat cluster chain first goes backwards(upto 3.5kb for fat16 file)
// and then forwards
//
	{
	TInt r=DeleteBackwardEntry(aName,aIsDir);
	test(r==KErrNone||r==KErrNotFound);
	CreateAlternate(_L("\\fat\\file1"),_L("\\fat\\file2"));
	RFile entry;
	if(aIsDir)
		{
		r=TheFs.MkDir(aName);
		if(r!=KErrNone)
			return(r);
		}
	else
		{
		r=entry.Create(TheFs,aName,EFileShareAny);
		if(r!=KErrNone)
			return(r);
		r=entry.SetSize(1);
		test(r==KErrNone);
		}
	RFile file3;
	r=file3.Create(TheFs,_L("\\fat\\file3"),EFileShareAny);
	test(r==KErrNone);
	r=file3.SetSize(EntriesPerFatSector()*gBytesPerCluster);
	test(r==KErrNone);
	r=TheFs.Delete(_L("\\fat\\file1"));
	test(r==KErrNone);
	file3.Close();
	if(aIsDir)
		ExpandDirectory(aName,aSize);
	else
		{
		r=entry.SetSize(aSize);
		test(r==KErrNone);
		entry.Close();
		}
	return(KErrNone);	
	}

LOCAL_C TInt DeleteStdEntry(const TDesC& aName,TBool aIsDir)
//
// Deletes entry with aName
//
	{
	if(aIsDir)
		return(TheFs.RmDir(aName));
	else
		return(TheFs.Delete(aName));
	}

LOCAL_C TInt CreateStdEntry(const TDesC& aName,TBool aIsDir,TInt aSize)
//
// Creates entry with aName where the cluster chain grows contiguously
//
	{
	TInt r=DeleteStdEntry(aName,aIsDir);
	test(r==KErrNone||r==KErrNotFound);
	if(aIsDir)
		{
		r=TheFs.MkDir(aName);
		if(r==KErrNone)
			ExpandDirectory(aName,aSize);
		return(r);
		}
	else
		{
		RFile file;
		r=file.Create(TheFs,aName,EFileShareAny);
		if(r==KErrNone)
			{
			r=file.SetSize(aSize);
			test(r==KErrNone);
			}
		else if(r==KErrAlreadyExists)
			{
			TInt res =file.Open(TheFs,aName,EFileShareAny);
			test(res==KErrNone);
			}
		else
			return(r);
		file.Close();
		return(r);
		}
	}

LOCAL_C TInt CreateEntry(const TDesC& aName,TBool aIsDir,TFatChain aChain,TInt aSize)
//
// Creates entry with aName whose fat cluster chain characteristics determined by aChain
//
	{
	switch(aChain)
		{
		case(EChainStd):return(CreateStdEntry(aName,aIsDir,aSize));
		case(EChainAlternate):return(CreateAlternateEntry(aName,aIsDir,aSize));
		case(EChainBackwards):return(CreateBackwardEntry(aName,aIsDir,aSize));
		case(EChainForwards):return(CreateForwardEntry(aName,aIsDir,aSize));
		default:return(KErrGeneral);
		}
	}

LOCAL_C TInt DeleteEntry(const TDesC& aName,TBool aIsDir,TFatChain aChain)
//
// Delete entry with aName
//
	{
	switch(aChain)
		{
		case(EChainStd):return(DeleteStdEntry(aName,aIsDir));
		case(EChainAlternate):return(DeleteAlternateEntry(aName,aIsDir));
		case(EChainBackwards):return(DeleteBackwardEntry(aName,aIsDir));
		case(EChainForwards):return(DeleteForwardEntry(aName,aIsDir));
		default:return(KErrGeneral);
		}
	}

LOCAL_C void TestRFsDelete(const TDesC& aName,TFatChain aChain,TInt aFileSize)
//
// test RFs::Delete
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Start(_L("TestRFsDelete"));
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aName,EFalse,aChain,aFileSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=TheFs.Delete(aName);
		if(r==KErrNone)
			break;
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
	test(r==KErrNone);
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(!EntryExists(aName));
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFsRmDir(const TDesC& aName,TFatChain aChain,TInt aDirSize)
//
// test RFs::RmDir
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFsRmDir"));
	switch (aChain)
		{
		case EChainStd:
			RDebug::Print(_L("Chain Std %S size %d"), &aName, aDirSize);
			break;
		case EChainAlternate:
			RDebug::Print(_L("Chain Alternate %S size %d"), &aName, aDirSize);
			break;
		case EChainBackwards:
			RDebug::Print(_L("Chain Backwards %S size %d"), &aName, aDirSize);
			break;
		case EChainForwards:
			RDebug::Print(_L("Chain Forwards %S size %d"), &aName, aDirSize);
			break;
		default:
			break;
		}
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aName,ETrue,aChain,aDirSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=TheFs.RmDir(aName);
		if(r==KErrNone)
			break;
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		RDebug::Print(_L("%6d: ScanDrive = %d"), __LINE__, r);
		if (r != KErrNone)
		{
			RDebug::Print(_L("ScanDrive fail %d"), r);
			DumpFat();
			DumpData(NULL, 0, 200);
		}
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		RDebug::Print(_L("%6d: CheckDisk = %d"), __LINE__, r);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
	test(r==KErrNone);
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(!EntryExists(aName));
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFsMkDir(const TDesC& aName)
//
// test RFs::MkDir
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFsMkDir"));
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=DeleteEntry(aName,ETrue,EChainStd);
		test(r==KErrNone||r==KErrNotFound);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=TheFs.MkDir(aName);
		if(r==KErrNone)
			break;
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
	test(r==KErrNone);
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aName));
	r=DeleteEntry(aName,ETrue,EChainStd);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFsRename(const TDesC& aOldName,const TDesC& aNewName,TBool aIsDir,TFatChain aChain,TInt aSize)
//
// test RFs::Rename
//
	{
	test.Next(_L("TestRFsRename"));
	TInt failCount=TheFailCount;
	TInt r;
	TEntry oldEntryInfo,newEntryInfo;
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aOldName,aIsDir,aChain,aSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		r=DeleteEntry(aNewName,aIsDir,aChain);
		test(r==KErrNone||r==KErrNotFound);
		GetEntryDetails(aOldName,oldEntryInfo);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=TheFs.Rename(aOldName,aNewName);
		if(r==KErrNone)
			break;
		if(r!=WriteFailValue)
			{
			test.Printf(_L("r=%d\n"),r);
			test(EFalse);
			}
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		// no start cluster if aSize==0
		if(aSize!=0)
			test(OneEntryExists(aOldName,aNewName));
		++failCount;
		}
	r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
	test(r==KErrNone);
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aNewName) && !EntryExists(aOldName));
	GetEntryDetails(aNewName,newEntryInfo);
	test(IsSameEntryDetails(oldEntryInfo,newEntryInfo));
	r=DeleteEntry(aNewName,aIsDir,aChain);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFsReplace(const TDesC& aOldName, const TDesC& aNewName,TBool aBothExist,TFatChain aChain,TInt aFileSize)
//
// test RFs::Replace
//
	{
	
	TInt failCount=TheFailCount;
	TInt r;
	if(aBothExist)
		test.Next(_L("TestRFsReplace with new name existing"));
	else
		test.Next(_L("TestRFsReplace with new name not existing"));
	TEntry oldEntryInfo,newEntryInfo;
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aOldName,EFalse,aChain,aFileSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		if(aBothExist)
			{
			r=CreateEntry(aNewName,EFalse,aChain,aFileSize);
			test(r==KErrNone||r==KErrAlreadyExists);
			}
		else
			{
			r=DeleteEntry(aNewName,EFalse,aChain);
			test(r==KErrNone||r==KErrNotFound);
			}
		GetEntryDetails(aOldName,oldEntryInfo);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=TheFs.Replace(aOldName,aNewName);
		if(r==KErrNone)
			break;
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		if(!aBothExist && aFileSize!=0)
			test(OneEntryExists(aOldName,aNewName));
		else if(aBothExist)
			test(EntryExists(aOldName)||EntryExists(aNewName));
		++failCount;
		}
	r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
	test(r==KErrNone);
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aNewName) && !EntryExists(aOldName));
	GetEntryDetails(aNewName,newEntryInfo);
	test(IsSameEntryDetails(oldEntryInfo,newEntryInfo));
	r=DeleteEntry(aNewName,EFalse,aChain);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFileCreate(const TDesC& aName)
//
// test RFile::Create
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileCreate"));
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=DeleteEntry(aName,EFalse,EChainStd);
		test(r==KErrNone||r==KErrNotFound);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		RFile file;
		r=file.Create(TheFs,aName,EFileShareAny);
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aName));
	r=DeleteEntry(aName,EFalse,EChainStd);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFileTemp(const TDesC& aPath)
//
// test RFile::Temp
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileTemp"));
	TFileName temp;
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		RFile file;
 		r=file.Temp(TheFs,aPath,temp,EFileShareAny);
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(temp));
	r=DeleteEntry(temp,EFalse,EChainStd);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFileRename(const TDesC& aOldName, const TDesC& aNewName,TFatChain aChain,TInt aFileSize)
//
// test RFile::Rename
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileRename"));
	TEntry oldEntryInfo,newEntryInfo;
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aOldName,EFalse,aChain,aFileSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		r=DeleteEntry(aNewName,EFalse,aChain);
		test(r==KErrNone||r==KErrNotFound);
		GetEntryDetails(aOldName,oldEntryInfo);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		RFile file;
		r=file.Open(TheFs,aOldName,EFileShareExclusive|EFileWrite);
		test(r==KErrNone);
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		r=file.Rename(aNewName);
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		test(r==WriteFailValue);
		file.Close();
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		if(aFileSize)
			test(OneEntryExists(aOldName,aNewName));
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aNewName) && !EntryExists(aOldName));
	GetEntryDetails(aNewName,newEntryInfo);
	test(IsSameEntryDetails(oldEntryInfo,newEntryInfo));
	r=DeleteEntry(aNewName,EFalse,aChain);
	test(r==KErrNone);
	++TheOpNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFileReplace(const TDesC& aName,TBool aAlreadyExists,TFatChain aChain,TInt aFileSize)
//
// test RFile::Replace
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileReplace"));
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		if(aAlreadyExists)
			{
			r=CreateEntry(aName,EFalse,aChain,aFileSize);
			test(r==KErrNone||r==KErrAlreadyExists);
			}
		else
			{
			r=DeleteEntry(aName,EFalse,aChain);
			test(r==KErrNone||r==KErrNotFound);
			}
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		RFile file;
		r=file.Replace(TheFs,aName,EFileShareAny);
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	test(EntryExists(aName));
	r=DeleteEntry(aName,EFalse,aChain);
	test(r==KErrNone);
	if(!aAlreadyExists)
		{
		++TheOpNumber;
		TheFailCount=0;
		}
	else
		{
		++TheFunctionNumber;
		TheOpNumber=TheFailCount=0;
		}
	}

LOCAL_C void TestRFileSetSize(const TDesC& aName,TFatChain aChain,TInt aOldFileSize,TInt aNewFileSize)
//
// test RFile::SetSize
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileSetSize"));
	test.Printf(_L("old size=%d new size=%d\n"),aOldFileSize,aNewFileSize);
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aName,EFalse,aChain,aOldFileSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		RFile file;
		r=file.Open(TheFs,aName,EFileShareAny|EFileWrite);
		test(r==KErrNone);
		r=file.SetSize(aNewFileSize);
		// close the file before testing the return value!
		file.Close();
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		file.Close();
		test(r==WriteFailValue);
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		r=file.Open(TheFs,aName,EFileShareAny|EFileWrite);
		test(r==KErrNone);
		TInt size;
		r=file.Size(size);
		test(r==KErrNone);
		test(size==aNewFileSize||size==aOldFileSize);
		file.Close();
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	RFile file;
	r=file.Open(TheFs,aName,EFileShareAny);
	test(r==KErrNone);
	TInt fileSize;
	r=file.Size(fileSize);
	test(r==KErrNone);
	test(aNewFileSize==fileSize);
	file.Close();
	r=DeleteEntry(aName,EFalse,aChain);
	test(r==KErrNone);
	++TheFunctionNumber;
	TheFailCount=0;
	}

LOCAL_C void TestRFileWrite(const TDesC& aName,TFatChain aChain,TInt aFileSize,TInt aPos,TInt aLength)
//
// test RFile::Write
//
	{
	TInt failCount=TheFailCount;
	TInt r;
	test.Next(_L("TestRFileWrite"));
	test.Printf(_L("aFileSize=%d,aPos=%d,aLength=%d\n"),aFileSize,aPos,aLength);
	TInt newSize=(aFileSize>=aPos+aLength)?aFileSize:aPos+aLength;
	HBufC8* desPtr;
	desPtr=HBufC8::New(aLength);
	test(desPtr!=NULL);
	TPtr8 des=desPtr->Des();
	des.SetLength(aLength);
	InitialiseWriteBuffer(des);
	FOREVER
		{
		test.Printf(_L("failCount=%d\n"),failCount);
		r=CreateEntry(aName,EFalse,aChain,aFileSize);
		test(r==KErrNone||r==KErrAlreadyExists);
		if(IsReset)
			{
			++TheFailCount;
			WriteLogFile();
			}
		r=SetWriteFailOn(failCount);
		test(r==KErrNone);
		RFile file;
		r=file.Open(TheFs,aName,EFileShareAny|EFileWrite);
		test(r==KErrNone);
		r=file.Write(aPos,des,aLength);
		if(r==KErrNone)
			{
			r=TheFs.ControlIo(gSessionPath[0]-'A',KControlIoWriteFailOff);
			test(r==KErrNone);
			file.Close();
			break;
			}
		test(r==WriteFailValue);
		file.Close();
		r=TheFs.ScanDrive(gSessionPath);
		test(r==KErrNone);
		r=TheFs.CheckDisk(gSessionPath);
		test(r==KErrNone);
		file.Open(TheFs,aName,EFileShareAny);
		test(r==KErrNone);
		TInt fileSize;
		r=file.Size(fileSize);
		// with fair scheduling enabled it's possible for the file 
		// size to grow even if the write appears to have failed...
//		test(fileSize==aFileSize||fileSize==newSize);
		test(fileSize>=aFileSize && fileSize <= newSize);

		file.Close();
		++failCount;
		}
	r=TheFs.CheckDisk(gSessionPath);
	test(r==KErrNone);
	RFile file;
	r=file.Open(TheFs,aName,EFileShareAny);
	test(r==KErrNone);
	TInt fileSize;
	r=file.Size(fileSize);
	test(r==KErrNone);
	test(newSize==fileSize);
	HBufC8* desPtr2;
	desPtr2=HBufC8::New(aLength);
	test(desPtr2!=NULL);
	TPtr8 des2=desPtr2->Des();
	des2.SetLength(aLength);
	r=file.Read(aPos,des2,des2.Length());
	test(r==KErrNone);
	r=des2.Compare(des);
	test(r==0);
	file.Close();
	r=DeleteEntry(aName,EFalse,aChain);
	test(r==KErrNone);
	delete desPtr;
	delete desPtr2;
	++TheFunctionNumber;
	TheFailCount=0;
	}


LOCAL_C void TestOperations(const TDesC& aOldName,const TDesC& aNewName,TFatChain aChain,TInt aFileSize, TInt aDirClusters)
//
// Tests the specified operations
//
	{
	TFileName oldDirName=aOldName;
	TFileName newDirName=aNewName;
	// create directory for directory operations
	oldDirName+=_L("\\");
	newDirName+=_L("\\");
	// locate path for RFile::Temp
	TInt pathPos=aOldName.LocateReverse('\\')+1;
	TFileName tempPath=aOldName.Left(pathPos);
	test.Printf(_L("aOldName=%S\n"),&aOldName);
	test.Printf(_L("aNewName=%S\n"),&aNewName);
	test.Printf(_L("tempPath=%S\n"),&tempPath);
	switch(TheOpNumber)
		{
		case(0):TestRFsDelete(aOldName,aChain,aFileSize);
		case(1):TestRFsRmDir(oldDirName,aChain,aDirClusters);
		case(2):TestRFsMkDir(oldDirName);
		case(3):TestRFsRename(aOldName,aNewName,EFalse,aChain,aFileSize);
		case(4):TestRFsRename(oldDirName,newDirName,ETrue,aChain,aDirClusters);
		case(5):TestRFsReplace(aOldName,aNewName,EFalse,aChain,aFileSize);
		case(6):TestRFsReplace(aOldName,aNewName,ETrue,aChain,aFileSize);
		case(7):TestRFileCreate(aOldName);
		case(8):TestRFileTemp(tempPath);
		case(9):TestRFileRename(aOldName,aNewName,aChain,aFileSize);
		case(10):TestRFileReplace(aOldName,EFalse,aChain,aFileSize);
		case(11):TestRFileReplace(aOldName,ETrue,aChain,aFileSize);break;
		default:test(EFalse);
		}
	test.End();
	}

LOCAL_C void TestOperation0()
//
//
//
	{
	// tests entries in root directory
	test.Next(_L("TestOperation0"));
	TestOperations(_L("\\entryWithTwoVfats"),_L("\\anotherEntryWithTwo"),EChainStd,0,0);
	}
	
LOCAL_C void TestOperation1()
//
//
//
	{
	// tests entries in a full root directory
	test.Next(_L("TestOperation1"));
	if(TheFailCount==0)
		FillUpRootDir(4);
	TestOperations(_L("\\entryOne"),_L("\\entryTwo"),EChainStd,512,0);
	UnFillUpRootDir(4);
	}

LOCAL_C void TestOperation2()
//
//
//
	{
	// tests entries in same subdir
	test.Next(_L("TestOperation2"));
	TestOperations(_L("\\test\\subdir1\\NameWithFourVFatEntriesWaffle"),_L("\\test\\subdir1\\aEntry"),EChainAlternate,5120,1);
	}


LOCAL_C void TestOperation3()
//
//
//
	{
	// tests entries in different subdir
	test.Next(_L("TestOperation3"));
	TestOperations(_L("\\test\\subdir1\\NameWithThreeEntries"),_L("\\ANother\\aEntrytwo"),EChainAlternate,15000,10);
	}


LOCAL_C void TestOperation4()
//
//
//
	{
	// tests entries with cluster chain of EChainForwards
	test.Next(_L("TestOperation4"));
	TestOperations(_L("\\test\\subdir1\\aEntry"),_L("\\aEntry"),EChainForwards,12799,25);
	}


LOCAL_C void TestOperation5()
//
//
//
	{
	// tests entries with cluster chain of EChainBackwards
	test.Next(_L("TestOperation5"));
	TestOperations(_L("\\test\\subdir1\\aEntry"),_L("\\ANother\\EntrywithThree"),EChainBackwards,51199,10);
	}

LOCAL_C void TestOperation6()
//
//
//
	{
	// tests entries where old name has a very long name
	test.Next(_L("TestOperation6"));
	TFileName longName=_L("\\test\\subdir1\\");
	MakeVeryLongName(longName);
	TestOperations(longName,_L("\\ANother\\e1"),EChainAlternate,5100,0);
	}

LOCAL_C void TestOperation7()
//
//
//
	{
	// tests entries where new name fills up subdir cluster
	test.Next(_L("TestOperation7"));
	TFileName name=_L("\\test\\subdir2\\");
	// add entry with 7 vfat entries
	MakeEntryName(name,80);
	if(TheFailCount==0)
		CreateEntry(name,EFalse,EChainStd,1);
	TestOperations(_L("\\test\\subdir2\\EntryWithThree"),_L("\\test\\subdir2\\EntryWithThree-"),EChainStd,512,0);
	DeleteEntry(name,EFalse,EChainStd);
	}

LOCAL_C void TestOperation8()
//
//
//
	{
	// tests entries where new name is first entry in new subdir cluster
	test.Next(_L("TestOperation8"));
	TFileName name=_L("\\test\\subdir2\\");
	// add entry with 10 vfat entries
	MakeEntryName(name,125);
	if(TheFailCount==0)
		CreateEntry(name,EFalse,EChainStd,175000);
	TestOperations(_L("\\test\\subdir2\\Entrywith3three"),_L("\\test\\subdir2\\entrywiththree-"),EChainStd,512,1);
	DeleteEntry(name,EFalse,EChainStd);
	}

GLDEF_C void DoTests()
	{
	TInt r;
	if(!IsReset && IsInternalRam())
		{
		test.Printf(_L("Error: Internal ram drive not tested\n"));
		return;
		}
	if(!IsReset)
		QuickFormat();

	DoReadBootSector();
	DumpBootSector();
	ClearDiskData();

	r=TheFs.SetSessionPath(gSessionPath);
	test(r==KErrNone);

	switch(TheFunctionNumber)
		{
		case(0):TestOperation0();
		case(1):{
				TestOperation1();
				r=TheFs.MkDir(_L("\\fat\\"));
				test(r==KErrNone);
				r=TheFs.MkDir(_L("\\test\\"));
				test(r==KErrNone);
				r=TheFs.MkDir(_L("\\ANother\\"));
				test(r==KErrNone);
				r=TheFs.MkDir(_L("\\test\\subdir1\\"));
				test(r==KErrNone);
				r=TheFs.MkDir(_L("\\test\\subdir2\\"));
				test(r==KErrNone);}
		case(2):{
				TestOperation2();
				// add some filler files
				CreateEntry(_L("\\test\\subdir1\\FillerOne"),EFalse,EChainStd,512);
				CreateEntry(_L("\\test\\subdir1\\FillerTwo"),EFalse,EChainStd,1024);}
		case(3):TestOperation3();
		case(4):{
				TestOperation4();
				// add some filler files
				CreateEntry(_L("\\ANother\\FillerThree"),EFalse,EChainStd,1536);
				CreateEntry(_L("\\test\\subdir1\\FillerFour"),EFalse,EChainStd,2048);}
		case(5):TestOperation5();
		case(6):TestOperation6();
		case(7):TestOperation7();
		case(8):TestOperation8();
		// increase size of file
		case(9):TestRFileSetSize(_L("\\entry1"),EChainStd,0,512);
		case(10):TestRFileSetSize(_L("\\entry1"),EChainAlternate,0,1025);
		case(11):TestRFileSetSize(_L("\\entry1"),EChainStd,1,512);
		// seek index (of CFatFileCB) resized
		case(12):TestRFileSetSize(_L("\\entry1"),EChainForwards,512,66666);
		// seek index resized
		case(13):TestRFileSetSize(_L("\\entry1"),EChainBackwards,32779,131074);
		// decrease size of file
		// seek index resized
		case(14):TestRFileSetSize(_L("\\entry1"),EChainForwards,133000,32768);
		// seek index resized
		case(15):TestRFileSetSize(_L("\\entry1"),EChainBackwards,65536,1);
		// seek index resized
		case(16):TestRFileSetSize(_L("\\entry1"),EChainAlternate,66554,0);
		case(17):TestRFileSetSize(_L("\\entry1"),EChainStd,1024,1);
		case(18):TestRFileSetSize(_L("\\entry1"),EChainAlternate,512,0);
		case(19):TestRFileWrite(_L("\\entry2"),EChainStd,0,0,512);
		case(20):TestRFileWrite(_L("\\entry2"),EChainAlternate,5120,512,1024);
		case(21):TestRFileWrite(_L("\\entry2"),EChainForwards,3584,3584,5000);
		case(22):TestRFileWrite(_L("\\entry2"),EChainBackwards,3000,2999,2000);
		// seek index resized
		case(23):TestRFileWrite(_L("\\entry2"),EChainBackwards,64000,64000,3000);
		// seek index resized
		case(24):TestRFileWrite(_L("\\entry2"),EChainForwards,131072,2,4000);break;
		default:test(EFalse);
		}
	DeleteEntry(_L("\\test\\subdir1\\FillerFour"),EFalse,EChainStd);
	DeleteEntry(_L("\\ANother\\FillerThree"),EFalse,EChainStd);
	DeleteEntry(_L("\\test\\subdir1\\FillerTwo"),EFalse,EChainStd);
	DeleteEntry(_L("\\test\\subdir1\\FillerOne"),EFalse,EChainStd);
	r=TheFs.RmDir(_L("\\test\\subdir2\\"));
	test(r==KErrNone);
	r=TheFs.RmDir(_L("\\test\\subdir1\\"));
	test(r==KErrNone);
	r=TheFs.RmDir(_L("\\ANother\\"));
	test(r==KErrNone);
	r=TheFs.RmDir(_L("\\test\\"));
	test(r==KErrNone);
	r=TheFs.RmDir(_L("\\fat\\"));
	test(r==KErrNone);
	if (gFatBuf)
		{
		delete gFatBuf;
		gFatBuf = NULL;
		}
	}
#endif
