// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// f32test\server\t_CHECKDISK.cpp
//
//
#define __E32TEST_EXTENSION__
#include <f32file.h>
#include <e32test.h>
//#include <e32svr.h>
//#include <f32dbg.h>
#include "t_server.h"
//#include <e32twin.h>

RTest test(_L("T_CHECKDISK"));

RFs TheFs;
TInt gDrive;
TFileName gSessionPath;
TChar gDriveToTest;


HBufC8* gBuf = NULL;
TPtr8 gBufReadPtr(NULL, 0);
HBufC8* gBufSec = NULL;
TPtr8 gBufWritePtr(NULL, 0);

const TInt KOneK = 1024;
const TInt KOneMeg = KOneK * 1024;
const TInt KBlockSize = KOneK * 129 ;
const TInt KWaitRequestsTableSize = 70;

TInt gBigFileSize = 0;
TInt gSmallFileSize = 0;
TInt64 gMediaSize = 0;
TBool gSkip=EFalse;
TInt writeSize = KBlockSize;
TInt seekSize = 0;
TSeek seekType = ESeekAddress;
TInt reduceSize = 0;

TTimeIntervalMicroSeconds32 gTimeTakenBigFile(0);
TBuf16<45> gSmallFile, gBigFile;
LOCAL_D TInt gNextFile=0;
TTime gTime1;
TTime gTime2;

LOCAL_D RSemaphore gSync;

// Concurrent Threads
RThread gBig;
RThread gSmall;
LOCAL_D RSemaphore client;
LOCAL_D const TInt KHeapSize=0x4000;
LOCAL_D const TInt KMaxHeapSize=0x100000;
TRequestStatus gStatus[KWaitRequestsTableSize];

enum TTestState
	{
	EThreadWait,
	EThreadSignal,
	ENoThreads
	};

//
// Generates a name of the form FFFFF*<aPos>.TXT (aLong.3)
//
GLDEF_C void FileNameGen(TDes16& aBuffer, TInt aLong, TInt aPos)
	{
	TInt padding;
	TInt i=0;
	TBuf16<10> tempbuf;

	_LIT(KNumber,"%d");
	tempbuf.Format(KNumber,aPos);
	padding=aLong-tempbuf.Size()/2;
	aBuffer=_L("");
	while(i<padding)
		{
		aBuffer.Append('F');
		i++;
		}
	aBuffer.Append(tempbuf);

	_LIT(KExtension1, ".TXT");
	aBuffer.Append(KExtension1);
	}




//
// Expects a drive letter as a parameter
//
LOCAL_C void parseCommandLine()
	{
	TBuf<0x100> cmd;
	User::CommandLine(cmd);
	TLex lex(cmd);
	TPtrC token=lex.NextToken();
	TInt r=0;

	if(token.Length()!=0)
		{
		gDriveToTest=token[0];
		gDriveToTest.UpperCase();
		}
	else
		{
		gDriveToTest='C';
		}

		r=TheFs.CharToDrive(gDriveToTest,gDrive);
		test_KErrNone(r);
		gSessionPath=_L("?:\\F32-TST\\");
		gSessionPath[0]=(TText)gDriveToTest;
		test.Printf(_L("\nCLP=%S\n"),&token);
	}

//
// Fills a buffer with character aC
//
LOCAL_C void FillBuffer(TDes8& aBuffer, TInt aLength, TChar aC)
	{
	test (aBuffer.MaxLength() >= aLength);
	for(TInt i=0; i<aLength; i++)
		{
		aBuffer.Append(aC);
		}
	}

//
// Waits for all the TRequestStatus in status[] to complete
//
LOCAL_C void WaitForAll(TRequestStatus status[], TInt aSize)
	{
	TInt i=0;

	while(i<aSize)
		{
		User::WaitForRequest(status[i++]);
		}
	}


//
// Writes a file synchronously in blocks of aBlockSize size
// this function can be called from another thread, therefore requires its own RTest instance
LOCAL_C TInt WriteFile(RFs& fs, TDes16& aFile, TInt aSize, TInt aBlockSize, TTestState aState)
	{
	RTest test(_L(""));

	TInt r=0;
	RFile fileWrite;

	test(aBlockSize>0);				// Block size must be greater than 0

	if(aState==EThreadWait)
		{
		gSync.Wait();
		}
	r=fileWrite.Replace(fs,aFile,EFileShareAny|EFileWrite);
	test_KErrNone(r);

	TInt j=0;
	while(j<aSize)
		{
		r=fileWrite.Write(gBufWritePtr, writeSize);
		test_KErrNone(r);
		if(seekType)
			{
			r=fileWrite.Seek(seekType, seekSize);
			test_KErrNone(r);

			if (writeSize + reduceSize >= 0)
				writeSize += reduceSize;

			writeSize = Min(writeSize, gBufWritePtr.Length());

			}
		if((j==0)&&(aState==EThreadSignal))
			{
			gSync.Signal();
			}
		j+=aBlockSize;
		}

	fileWrite.Close();

	return KErrNone;
	}

LOCAL_C void IniStatus(TRequestStatus aStatus[], TInt aSize)
	{
	TInt i=0;

	while(i<aSize)
		{
		aStatus[i++]=KRequestPending;
		}
	}

//
// Write big file
// This is a thread function, therefore requires its own RTest instance
LOCAL_C TInt WriteBigFile(TAny* )
	{
	RTest test(_L(""));
	RFs fs;
	TInt r=fs.Connect(gDriveToTest);
	test_KErrNone(r);

	r=fs.SetSessionPath(gSessionPath);
	test_KErrNone(r);

	WriteFile(fs, gBigFile, gBigFileSize, KBlockSize, EThreadSignal);
	gTime1.HomeTime();

	client.Signal();
	return ETrue;
	}

//
// Writes a file asynchronously in blocks of aBlockSize size
// this function can be called from another thread, therefore requires its own RTest instance
LOCAL_C void WriteFileAsync(RFs& fs, RFile& aFileWrite, TDes16& aFile, TInt aSize, TInt aBlockSize, TRequestStatus aStatus[])
	{
	RTest test(_L(""));

	TInt r=0;

	test(aBlockSize>0);				// Block size must be greater than 0
	test((aSize%aBlockSize)==0); 	// Ensure the size of the file is a multiple of the block size

	r=aFileWrite.Replace(fs,aFile,EFileShareAny|EFileWrite);
	test_KErrNone(r);

	TInt j=0,i=0;
	while(j<aSize)
		{
		aFileWrite.Write(gBufWritePtr,aStatus[i++]);
		j+=aBlockSize;
		}
	}



//
// Write big file async
// This is a thread function, therefore requires its own RTest instance
LOCAL_C TInt WriteBigFileAsync(TAny* )
	{
	RTest test(_L(""));
	RFs fs;
	TInt r=fs.Connect(gDriveToTest);
	test_KErrNone(r);

	r=fs.SetSessionPath(gSessionPath);
	test_KErrNone(r);


	RFile bigFile;

	IniStatus(gStatus,KWaitRequestsTableSize);
	WriteFileAsync(fs, bigFile, gSmallFile, gBigFileSize, KBlockSize,gStatus);
	gSync.Signal();
	WaitForAll(gStatus, gBigFileSize/KBlockSize);

	return ETrue;
	}

static void TestClientDies()
	{
	test.Next(_L("Client dying unexpectedly"));

	TInt r=0;
	TBuf<20> unit=_L("?:\\");
	unit[0]=(TText)gDriveToTest;

	//-------------------------------------------
	test.Printf(_L("Sync test\n"));

	r=gBig.Create(_L("TEST1"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);

	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Async test\n"));
	r=gSmall.Create(_L("TEST2"),WriteBigFileAsync,KDefaultStackSize*2,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);

	gSmall.Resume();
	gSync.Wait();

	gSmall.Kill(-2);
	gSmall.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing for size not multiple of blocksize\n"));

	writeSize = 5000;
	r=gBig.Create(_L("TEST3"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and write inside the file boundary\n"));

	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -5000;
	reduceSize = -3000;
	r=gBig.Create(_L("TEST4"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and overwrite entire file\n"));
	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -5000;
	reduceSize = 0;
	r=gBig.Create(_L("TEST5"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and write outside the file boundary\n"));

	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -5000;
	reduceSize = 5000;
	r=gBig.Create(_L("TEST6"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and write within the file boundary\n"));
	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -3000;
	reduceSize = -4000;
	r=gBig.Create(_L("TEST7"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and write exactly to file size\n"));
	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -3000;
	reduceSize = -3000;
	r=gBig.Create(_L("TEST8"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	//-------------------------------------------
	test.Printf(_L("Testing with seek current and write greater than file size\n"));
	writeSize = 5000;
	seekType = ESeekCurrent;
	seekSize = -3000;
	reduceSize = 10000;
	r=gBig.Create(_L("TEST9"),WriteBigFile,KDefaultStackSize,KHeapSize,KMaxHeapSize,NULL);
	test_KErrNone(r);
	gBig.Resume();
	gSync.Wait();

	gBig.Kill(-2);
	gBig.Close();
	User::After(500000);

	r=TheFs.CheckDisk(unit);
	test_Value(r, r == KErrNone || r == KErrNotSupported);

	TInt retries = 0;

	do
		{
		r=TheFs.ScanDrive(unit);
		if (r != KErrNone)
			test.Printf(_L("ScanDrive() returned %d\n"), r);
		if (r == KErrInUse)
			User::After(500000);
		}
	while (r == KErrInUse && ++retries < 5);

	test_Value(r, r == KErrNone || r == KErrNotSupported);

	}


//---------------------------------------------------------------------
/**
    Test that CheckDisk will not cause stack overflow on very deep directory structure.
*/
void TestCheckDisk_VeryDeepDirectory()
	{
	test.Next(_L("Testing deep dir structure check"));

	TInt nRes;
	TBuf<20> unit=_L("?:\\");
	unit[0]=(TText)gDriveToTest;

	//-- 1. create deep dir structure, like \\0\\1\\2\\...... 90 levels deep
	const TInt KMaxDirDepth = 90;

	test.Printf(_L("Creating directory with %d subdirs.\n"),KMaxDirDepth);

	TFileName fn;
	for(TInt i=0; i<KMaxDirDepth; ++i)
		{
		fn.AppendFormat(_L("\\%d"), i%10);
		}
	fn.Append(_L("\\"));

	nRes = TheFs.MkDirAll(fn);
	test_Value(nRes, nRes == KErrNone || nRes == KErrAlreadyExists);

	//-- 2. invoke Check Disk and ensure that target doesn't die from stack overflow.
	test.Printf(_L("Running Check Disk...\n"));
	nRes = TheFs.CheckDisk(unit);

	test_Value(nRes, nRes == KErrNone || nRes == KErrTooBig);
	}


//---------------------------------------------------------------------
//
// This test tries to read a small file while writing a big one
//
GLDEF_C void CallTestsL()
	{
	TBuf16<45> dir;

	test.Next(_L("Preparing the environmnet\n"));

	FileNameGen(gSmallFile, 8, gNextFile++);
	FileNameGen(gBigFile, 8, gNextFile++);
	dir=gSessionPath;
	dir.Append(gSmallFile);
	gSmallFile=dir;
	dir=gSessionPath;
	dir.Append(gBigFile);
	gBigFile=dir;

	TRAPD(res,gBuf = HBufC8::NewL(KBlockSize+1));
	test(res == KErrNone && gBuf != NULL);

	gBufWritePtr.Set(gBuf->Des());
	FillBuffer(gBufWritePtr, KBlockSize, 'B');

	TRAPD(res2,gBufSec = HBufC8::NewL(KBlockSize+1));
	test(res2 == KErrNone && gBufSec != NULL);
	gBufReadPtr.Set(gBufSec->Des());

	//---------------------------

	TestClientDies();
	TestCheckDisk_VeryDeepDirectory();

	delete gBuf;
	delete gBufSec;
	}

LOCAL_C void DoTests()
	{
 	TInt r=0;

 	r=client.CreateLocal(0);
	test_KErrNone(r);

  	r=gSync.CreateLocal(0);
	test_KErrNone(r);

	r=TheFs.SetSessionPath(gSessionPath);
	test_KErrNone(r);

	r=TheFs.MkDirAll(gSessionPath);
	test_Value(r, r == KErrNone || r == KErrAlreadyExists);

	TheFs.ResourceCountMarkStart();
	TRAP(r,CallTestsL());
	test_KErrNone(r);
	TheFs.ResourceCountMarkEnd();
	}

TBool CheckForDiskSize()
	{
	TVolumeInfo volInfo;
	TInt r = TheFs.Volume(volInfo, gDrive);
	test_KErrNone(r);

	gMediaSize = volInfo.iSize;
	gSmallFileSize = KBlockSize;
	gBigFileSize = KBlockSize*20;
	while(((2*gBigFileSize)+KOneMeg) > gMediaSize )
		{
			gBigFileSize -= (2*KBlockSize);
		}

	if(gBigFileSize< (3*gSmallFileSize))
		return EFalse;
	else
		return ETrue;
	}

void Format(TInt aDrive)
	{

	test.Next(_L("Format"));
	TBuf<4> driveBuf=_L("?:\\");
	driveBuf[0]=(TText)(aDrive+'A');
	RFormat format;
	TInt count;
	TInt r=format.Open(TheFs,driveBuf,EQuickFormat,count);
	test_KErrNone(r);

	while(count)
		{
		TInt r=format.Next(count);
		test_KErrNone(r);
		}
	format.Close();
	}


static TBool IsFAT(RFs &aFsSession, TInt aDrive)
{
	_LIT(KFatName, "Fat");

	TFileName f;
	TInt r = aFsSession.FileSystemName(f, aDrive);
	test_Value(r, r == KErrNone || r == KErrNotFound);
	return (f.CompareF(KFatName) == 0);
}


GLDEF_C TInt E32Main()
	{

	CTrapCleanup* cleanup;
	cleanup=CTrapCleanup::New();

	__UHEAP_MARK;
	test.Title();
	test.Start(_L("Starting tests..."));
	parseCommandLine();

	TInt r = TheFs.Connect();
	test_KErrNone(r);

	TDriveInfo info;
	TVolumeInfo volInfo;
	r=TheFs.Drive(info,gDrive);
	test_KErrNone(r);

	if(info.iMediaAtt&KMediaAttVariableSize)
		{// Not testing in RAM Drives
		test.Printf(_L("Tests skipped in RAM drive\n"));
		goto out;
        }

	r = TheFs.Volume(volInfo, gDrive);
	if (r == KErrNotReady)
		{
		if (info.iType == EMediaNotPresent)
			test.Printf(_L("%c: Medium not present - cannot perform test.\n"), (TUint)gDriveToTest);
		else
			test.Printf(_L("%c: medium found (type %d) but drive not ready\nPrevious test may have hung; else, check hardware.\n"), (TUint)gDriveToTest, (TInt)info.iType);
		}
	else if (r == KErrCorrupt)
		{
		test.Printf(_L("%c: Media corruption; previous test may have aborted; else, check hardware\n"), (TUint)gDriveToTest);
		}
	test_KErrNone(r);

	if(!IsFAT(TheFs, gDrive))
        {
		test.Printf(_L("Tests skipped on non-FAT drive\n"));
		goto out;
        }

	if ((volInfo.iDrive.iMediaAtt & KMediaAttFormattable))
		Format(gDrive);

	if(CheckForDiskSize())
		{
		DoTests();
		if ((volInfo.iDrive.iMediaAtt & KMediaAttFormattable))
		   Format(gDrive);
		}
	else
		{
		test.Printf(_L("Skipping tests due to lack of space to perform them in this unit\n"));
		}
out:
	test.End();

	TheFs.Close();
	test.Close();

	__UHEAP_MARKEND;
	delete cleanup;
	return(KErrNone);
    }
