// 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:
// f32test\server\t_mount.cpp
// Testing some issues connected with mounting/dismounting file fystems, drives finalisation etc.
//
//

/**
 @file
*/

#define __E32TEST_EXTENSION__

#include <f32file.h>
#include <e32test.h>
#include <e32math.h>
#include <e32property.h>
#include <f32dbg.h>

#include "t_server.h"
#include "fat_utils.h"

using namespace Fat_Test_Utils;

#ifdef __VC32__
    // Solve compilation problem caused by non-English locale
    #pragma setlocale("english")
#endif

RTest test(_L("T_Mount"));

static TInt     gDriveNum=-1; ///< drive number we are dealing with
static TInt64   gRndSeed;


const TInt KBM_Repetitions = 5;     ///< number of repetitions for BM testing
const TUint32 KCheckFileSize = 533; ///< size of the small file to be deleted

_LIT(KFileNameFirst,  "\\FIRST%d.DAT");
_LIT(KFileNameMiddle, "\\MID%d.DAT");
_LIT(KFileNameLast,   "\\LAST%d.DAT");
_LIT(KFileNameFiller, "\\FILL%d.DAT");

typedef void (*TStressFN)(TInt);    //-- pointer to the FAT mount stress function


//-------------------------------------------------------------------
//-- debug bit flags that may be set in the property which controls FAT volume mounting

const TUid KThisTestSID={0x10210EB3}; ///< this EXE SID

const TUint32 KMntProp_EnableALL    = 0x00000000; //-- enable all operations

#ifdef _DEBUG

const TUint32 KMntProp_DisableALL   = 0xFFFFFFFF; //-- disable all operations
const TUint32 KMntProp_Disable_FsInfo       = 0x00000001; //-- mask for disabling/enabling FSInfo information
const TUint32 KMntProp_Disable_FatBkGndScan = 0x00000002; //-- mask for disabling/enabling FAT background scanner

#endif

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

/** set a debug property value, which then wiil be read by FAT fsy */
void SetFsyDebugFlag(TInt aDriveNo, TUint32 aFlags)
{
    TInt nRes;

    nRes = RProperty::Set(KThisTestSID, aDriveNo, aFlags);
    test_KErrNone(nRes);
}

//-------------------------------------------------------------------
/**
    format the volume and read the boot sector
*/
static void FormatVolume(TBool aQuickFormat)
{
    TInt nRes;

    #if 0
    //-- FAT32 SPC:1; for the FAT32 testing on the emulator
    (void)aQuickFormat;

    #ifdef  __EPOC32__
    test.Printf(_L("This is emulator configuration!!!!\n"));
    test(0);
    #endif

    TFatFormatParam fp;
    fp.iFatType = EFat32;
    fp.iSecPerCluster = 1;
    nRes = FormatFatDrive(TheFs, CurrentDrive(), ETrue, &fp); //-- always quick; doesn't matter for the emulator
    #else
    nRes = FormatFatDrive(TheFs, CurrentDrive(), aQuickFormat);
    #endif

    test_KErrNone(nRes);

}

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

/**
    Prepare FAT volume for mounting performance testing

    1. quick format the drive
    2. create KBM_Repetitions files in the beginning (first files)
    3. create KMaxFillFiles/2 large files (they take about 40% of the volume);
    4. create KBM_Repetitions files (middle files)
    5. create KMaxFillFiles/2 large files (they take other 40% of the volume);
    6. create KBM_Repetitions files (last files)

    @return ETrue if everythis is OK
*/
TBool PrepareVolumeForBM()
{
    test.Printf(_L("Prepare the volume for BM testing...\n"));

    TInt nRes;
    TInt i;

    //-- 1. quick format the drive
    FormatVolume(ETrue);


    if(!Is_Fat32(TheFs, gDriveNum))
    {
        test.Printf(_L("This test requires FAT32 ! Skipping.\n"));
        return EFalse;
    }

    TVolumeInfo volInfo;
    nRes = TheFs.Volume(volInfo, gDriveNum);
    test_KErrNone(nRes);

    //-- the files will take 80% of the drive space
    const TInt    KMaxFillFiles = 100;
    const TUint32 KFillFileSz = (TUint32)((volInfo.iFree*8) / (10*KMaxFillFiles));

    //-- 2. create KBM_Repetitions files in the very begining (occupies first FAT entries)
    TBuf<64> buf;

    for(i=0; i<KBM_Repetitions; ++i)
    {
        buf.Format(KFileNameFirst, i);
        nRes = CreateCheckableStuffedFile(TheFs, buf, KCheckFileSize);
        test_KErrNone(nRes);
    }

    //-- 3. Fill the FAT with entries and cteate a file in the middle
    const TInt nHalf1 = KMaxFillFiles / 2;

    for(i=0; i<nHalf1; ++i)
    {
        buf.Format(KFileNameFiller, i);
        nRes = CreateEmptyFile(TheFs, buf, KFillFileSz);
        test_KErrNone(nRes);
    }

    //-- 4. create a files in the middle
    for(i=0; i<KBM_Repetitions; ++i)
    {
        buf.Format(KFileNameMiddle, i);
        nRes = CreateCheckableStuffedFile(TheFs, buf, KCheckFileSize);
        test_KErrNone(nRes);
    }

    //-- 5. fill second half FAT
    for(i=nHalf1; i<KMaxFillFiles; ++i)
    {
        buf.Format(KFileNameFiller, i);
        nRes = CreateEmptyFile(TheFs, buf, KFillFileSz);
        test_KErrNone(nRes);
    }


    //-- 6. create files in the very end (occupiy last FAT entries)
    for(i=0; i<KBM_Repetitions; ++i)
    {
        buf.Format(KFileNameLast, i);
        nRes = CreateCheckableStuffedFile(TheFs, buf, KCheckFileSize);
        test_KErrNone(nRes);
    }

    return ETrue;
}

/**
    Mounts and dismounts FAT volume several times calculating average time taken to mount.
    Also can stress FS by calling stress function that can do some work on the volume.

    @param  apStressFN pointer to the stressing function which can be called just after mounting. Can be NULL.
    @return time in milliseconds taken to mout the volume
*/
static TUint32 DoMeasureMountTime(TStressFN apStressFN)
{

    TInt nRes;

    TTime   timeStart;
    TTime   timeEnd;

    TInt64   usMountTime=0;      //-- total time taken by "Mount"

    //-- disable FAT test utils print out, it can affects measured time
    EnablePrintOutput(EFalse);

    for(TInt i=0; i<KBM_Repetitions; ++i)
    {

        //-- A. remount FS taking the time when mounting starts
        nRes = RemountFS(TheFs, gDriveNum, &timeStart);
        test_KErrNone(nRes);

        //-- B. call a given stress function
        if(apStressFN)
            apStressFN(i);

        //-- C. take end time
        timeEnd.UniversalTime();
        test_KErrNone(nRes);

        usMountTime += (timeEnd.MicroSecondsFrom(timeStart)).Int64();
     }

    const TUint32 msMountTime = (TUint32)usMountTime / (K1mSec*KBM_Repetitions);

    EnablePrintOutput(ETrue); //-- Enable FAT test utils print out

    return msMountTime;
}

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

/**
    Stress function.
    Use case: read access to the first file on the volume in root dir.
*/
static void FirstReadAccess_FirstFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameFirst, aRepNo);

    TInt nRes = VerifyCheckableFile(TheFs, buf);
    test_KErrNone(nRes);
}


/**
    Stress function.
    Use case: read access to the middle file on the volume in root dir.
*/
static void FirstReadAccess_MiddleFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameMiddle, aRepNo);

    TInt nRes = VerifyCheckableFile(TheFs, buf);
    test_KErrNone(nRes);
}

/**
    Stress function.
    Use case: read access to the last file on the volume in root dir.
*/
static void FirstReadAccess_LastFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameLast, aRepNo);

    TInt nRes = VerifyCheckableFile(TheFs, buf);
    test_KErrNone(nRes);
}

/**
    Stress function.
    Use case: Getting volume information SYNCHRONOUSLY.
*/
static void GetVolInfo_Synch_Stress(TInt)
{
    TVolumeInfo volInfo;
    TInt nRes = TheFs.Volume(volInfo, gDriveNum);
    test_KErrNone(nRes);
}

/**
    Stress function.
    Use case: Getting volume information _ASYNCHRONOUSLY_, i.e. do not wait until
    FAT32 free space scan thread finishes
*/
static void GetVolInfo_Asynch_Stress(TInt)
{
    TVolumeInfo volInfo;

    //-- let's use special version of the RFS API
    TRequestStatus rqStat;
    TheFs.Volume(volInfo, gDriveNum, rqStat);

    User::WaitForRequest(rqStat);
    test(rqStat.Int()==KErrNone);

}

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

/**
    Stress function.
    Use case: deletes files in the beginning of the volume (or FAT table)
*/
static void DeleteFirstFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameFirst, aRepNo);

    TInt nRes = TheFs.Delete(buf);
    test_KErrNone(nRes);
}


/**
    Stress function.
    Use case: deletes files in the middle of the volume (or FAT table)
*/
static void DeleteMiddleFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameMiddle, aRepNo);

    TInt nRes = TheFs.Delete(buf);
    test_KErrNone(nRes);
}

/**
    Stress function.
    Use case: deletes files in the end of the volume (or FAT table)
*/
static void DeleteLastFile_Stress(TInt aRepNo)
{
    TBuf<64> buf;
    buf.Format(KFileNameLast, aRepNo);

    TInt nRes = TheFs.Delete(buf);
    test_KErrNone(nRes);
}


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

/**
    perform series of tests that measure and print out FAT volume mount time for different secenarios
*/
void MeasureMountTime()
{
    TUint32 msTime;

    if(!PrepareVolumeForBM()) //-- prepare the volume for BM tests: format it, create file structure etc.
        return;

    //-- 1. no stress function, measure just pure mount time
    msTime = DoMeasureMountTime(NULL);
    test.Printf(_L("#--> Pure mount:%d ms\n"), msTime);

    //-- 2.1 measure mount time with a read-acess to the first file on the volume
    msTime = DoMeasureMountTime(FirstReadAccess_FirstFile_Stress);
    test.Printf(_L("#--> mount and read access to 1st file:%d ms\n"), msTime);

    //-- 2.2 measure mount time with a read-acess to the middle file on the volume
    msTime = DoMeasureMountTime(FirstReadAccess_MiddleFile_Stress);
    test.Printf(_L("#--> mount and read access to middle file:%d ms\n"), msTime);

    //-- 2.3 measure mount time with a read-acess to the last file on the volume
    msTime = DoMeasureMountTime(FirstReadAccess_LastFile_Stress);
    test.Printf(_L("#--> mount and read access to last file:%d ms\n"), msTime);

    //-- 2.4 measure mount time with getting a volume information
    msTime = DoMeasureMountTime(GetVolInfo_Synch_Stress);
    test.Printf(_L("#--> mount and getting volInfo (synch):%d ms\n"), msTime);

    msTime = DoMeasureMountTime(GetVolInfo_Asynch_Stress);
    test.Printf(_L("#--> mount and getting volInfo (Asynch):%d ms\n"), msTime);


    //-- 2.4 measure mount time with deleting file in the beginning (write access to the first entries of the FAT32)
    msTime = DoMeasureMountTime(DeleteFirstFile_Stress);
    test.Printf(_L("#--> mount and delete first file:%d ms\n"), msTime);

    //-- 2.5 measure mount time with deleting file in the middle (write access to the middle entries of the FAT32)
    msTime = DoMeasureMountTime(DeleteMiddleFile_Stress);
    test.Printf(_L("#--> mount and delete middle file:%d ms\n"), msTime);

    //-- 2.6 measure mount time with deleting file in the end (write access to the last entries of the FAT32)
    msTime = DoMeasureMountTime(DeleteLastFile_Stress);
    test.Printf(_L("#--> mount and delete last file:%d ms\n"), msTime);

    test.Printf(_L("---\n"), msTime);
}

//----------------------------------------------------------------------------------------------
//! @SYMTestCaseID      PBASE-T_MOUNT-0521
//! @SYMTestType        PT
//! @SYMPREQ            PREQ1721
//! @SYMTestCaseDesc    Testing FAT volume mount performance for various scenarios
//!
//! @SYMTestActions
//!             0   Prepare the volume by formatting it and creating 100 files to occupy the space.
//!             1   Turn OFF all mount enhancements
//!             2   Measure and print out volume mount time for the next scenarios:
//!                   a.  simple mount
//!                   b.  mount and read access to the media (reading the last file in the root dir.)
//!                   c.  mount and write access to the media (writing data into the last file in the root dir)
//!                   d.  mount getting volume information
//!
//!
//!             3     Turn ON using FSInfo.
//!             4     Repeat step 2 for this case.
//! @SYMTestExpectedResults Finishes ok.
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented
//----------------------------------------------------------------------------------------------
void TestFAT_Mounting_Performance()
{
    test.Next(_L("\n#--> Measuring FAT volumes mount performance.\n"));
#ifndef _DEBUG
    test.Printf(_L("Skipping the test in the Release build! \n"));
    return;
#else

   //-- 1. turn OFF all mount enhancements like using FSInfo, backround FAT scan etc.
   test.Printf(_L("#--> ==== All mount enhancements are disabled ====\n"));
   SetFsyDebugFlag(gDriveNum, KMntProp_DisableALL);
   MeasureMountTime();

   //-- 2. Turn ON using FSInfo
   test.Printf(_L("#--> ==== Enabled Using FSInfo ====\n"));
   SetFsyDebugFlag(gDriveNum, KMntProp_DisableALL & ~KMntProp_Disable_FsInfo);
   MeasureMountTime();

   //-- 2. Turn OFF using FSInfo and ON FAT32 bacground scanning
   test.Printf(_L("#--> ==== Enabled FAT32 BkGnd scan, FSInfo disabled ====\n"));
   SetFsyDebugFlag(gDriveNum, KMntProp_DisableALL & ~KMntProp_Disable_FatBkGndScan);
   MeasureMountTime();

   //-- restore mounting mechanism
   SetFsyDebugFlag(gDriveNum, KMntProp_EnableALL);

#endif //_DEBUG
}



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

/**
    Check if the drive aDriveNo is finalised or not.
    The "CleanShutDown" is obtained by QueryVolumeInfoExt API and by reading 2 FATs directly with checking their consistence.

    @param  aDriveNo drive number to query.
    @return ETrue if the drive if finalised
*/
static TBool DoCheckVolumeFinalised(TInt aDriveNo)
{
    TInt nRes;
    TPckgBuf<TBool> boolPckg;

    //-- 1. get "Finalised" state by using the API
    nRes = TheFs.QueryVolumeInfoExt(aDriveNo, EIsDriveFinalised, boolPckg);
    test_KErrNone(nRes);

    //-- N.B. for FAT12 the result can be either OK or "NotSupported"
    //-- If FAT12 is in explicit "finalised" state, the result will be OK
    //-- if not, we can't query the volume state, because FAT12 doesn't support flags in FAT[1]

    const TBool bFinalised_From_API = boolPckg() >0;
          TBool bFinalised_From_FAT1 = bFinalised_From_API;

    TBuf8<32> fatBuf(32);
    TFatBootSector  bootSec;
    const TUint32 posMainBootSec = KBootSectorNum << KDefaultSectorLog2;

    nRes = ReadBootSector(TheFs, gDriveNum, posMainBootSec, bootSec);
    test_KErrNone(nRes);

    test(bootSec.IsValid());

    const TUint32 Fat1StartPos = bootSec.FirstFatSector() * bootSec.BytesPerSector();

    if(bootSec.FatType() == EFat16)
    {//-- FAT16
        TUint16 fatEntry;
        const TUint16 KClnShtdnMask = 0x8000; //-- "ClnShutBitMask", see FAT specs

        //-- read "CleanShutDown" flag directly from the 1st FAT
        nRes = MediaRawRead(TheFs, gDriveNum, Fat1StartPos, fatBuf.Size(), fatBuf);
        test_KErrNone(nRes);

        Mem::Copy(&fatEntry, (fatBuf.Ptr()+sizeof(fatEntry)), sizeof(fatEntry));
        bFinalised_From_FAT1 = (fatEntry & KClnShtdnMask) >0;


        for(TInt i=1; i<bootSec.NumberOfFats(); ++i)
        {
            //-- read a flag from the next FAT
            const TUint32 currFatStartPos = (bootSec.FirstFatSector() + i*bootSec.TotalFatSectors())*bootSec.BytesPerSector();

            nRes = MediaRawRead(TheFs, gDriveNum, currFatStartPos, fatBuf.Size(), fatBuf);
            test_KErrNone(nRes);

            Mem::Copy(&fatEntry, (fatBuf.Ptr()+sizeof(fatEntry)), sizeof(fatEntry));
            const TBool bFinalised_From_currFAT = (fatEntry & KClnShtdnMask)>0;

            test(bFinalised_From_currFAT == bFinalised_From_FAT1);
        }

    }
    else if(bootSec.FatType() == EFat32)
    {//-- FAT32
        TUint32 fatEntry;
        const TUint32 KClnShtdnMask = 0x08000000; //-- "ClnShutBitMask", see FAT specs

        //-- read "CleanShutDown" flag directly from the 1st FAT
        nRes = MediaRawRead(TheFs, gDriveNum, Fat1StartPos, fatBuf.Size(), fatBuf);
        test_KErrNone(nRes);

        Mem::Copy(&fatEntry, (fatBuf.Ptr()+sizeof(fatEntry)), sizeof(fatEntry));
        bFinalised_From_FAT1 = (fatEntry & KClnShtdnMask) >0;

        for(TInt i=1; i<bootSec.NumberOfFats(); ++i)
        {
            //-- read a flag from the next FAT
            const TUint32 currFatStartPos = (bootSec.FirstFatSector() + i*bootSec.TotalFatSectors())*bootSec.BytesPerSector();

            nRes = MediaRawRead(TheFs, gDriveNum, currFatStartPos, fatBuf.Size(), fatBuf);
            test_KErrNone(nRes);

            Mem::Copy(&fatEntry, (fatBuf.Ptr()+sizeof(fatEntry)), sizeof(fatEntry));
            const TBool bFinalised_From_currFAT = (fatEntry & KClnShtdnMask) >0;

            test(bFinalised_From_currFAT == bFinalised_From_FAT1);
        }

    }
    else  //-- FAT12
    {//-- FAT12 doesn't have flags in FAT[1]
     bFinalised_From_FAT1 = bFinalised_From_API;
    }

    test(bFinalised_From_FAT1 == bFinalised_From_API);

    return bFinalised_From_API;
}



//----------------------------------------------------------------------------------------------
//! @SYMTestCaseID      PBASE-T_MOUNT-0522
//! @SYMTestType        UT
//! @SYMPREQ            PREQ1721
//! @SYMTestCaseDesc    RFs::FinaliseDrive() and RFs::FinaliseDrives() API
//!
//! @SYMTestActions
//!             0   Finalise the drive in RW mode and check the result.
//!             1   Finalise the drive in RW mode once again and check the result.
//!             2   Create a file and check that the volume has become unfinalised.
//!             3   Open a file, try to finalise the volume; it shall fail with KErrInUse
//!             4   close the file, finalise, check that the result is OK.
//!             5   "Unfinalise" the volume; check that the volume is not finalised any longer.
//!             6   Finalise the drive in RO mode and check the result.
//!             7   Try to create a file, shall fail with KErrAccessDenied
//!             8   Try to finalise into RW mode, shall fail with KErrAccessDenied
//!             9   Try to unfinalise volume, it shall remain RO
//!             10  Remount the drive, it shall become RW and finalised
//!             11  Test transition "Not finalised" -> EFinal_RW (expected: KErrNone)
//!             12  Test transition EFinal_RW -> EFinal_RO  (expected: KErrNone)
//!             13  Test transition EFinal_RO -> EFinal_RW  (expected: KErrAccessDenied)
//!             14  Remount the volume to reset RO flag
//!             15  test old RFs::FinaliseDrives() API by finalising all drives in the system
//!
//! @SYMTestExpectedResults finishes if the volume finalisation works correctly. panics otherwise
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented
//----------------------------------------------------------------------------------------------
static void TestFinaliseFS()
{
    test.Next(_L("Testing RFs::FinaliseDrives() API\n"));

    if(!Is_Fat16(TheFs, gDriveNum) && !Is_Fat32(TheFs, gDriveNum))
    {
        test.Printf(_L("This step requires FAT16 or FAT32 ! Skipping.\n"));
        return;
    }

    TInt  nRes;
    TBool bDriveFinalised;

    //============= 1. finalise the drive (RW mode) and check the result
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //-- 1.1 finalise the drive second time EFinal_RW -> EFinal_RW shall work
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //============= 2. create a file. Shall succeed (EFinal_RW), the volume shall become unfinalised

    RFile file;
    _LIT(KFileName, "\\my_file1.dat");

    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(!bDriveFinalised); //-- the volume has become "unfinalised"

    //-- 2.1 open a file, try to finalise; Shall dail with KErrInUse
    nRes = file.Replace(TheFs, KFileName, EFileWrite | EFileRead);
    test_KErrNone(nRes);

    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test(nRes==KErrInUse); //-- can't finalise drives with opened objects

    file.Close();

    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //============= 3. test "unfinalise API"
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EForceUnfinalise);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(!bDriveFinalised); //-- the volume has become "unfinalised"

    //============= 4. test finalisation into RO mode
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RO); //-- the volume becomes RO
    test_KErrNone(nRes);

    //-- try to write a file on RO volume; it shall fail with KErrAccessDenied
    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test(nRes == KErrAccessDenied);
    file.Close();

    //-- 4.1 try to finalise into EFinal_RW mode, shall fail with KErrAccessDenied
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test(nRes == KErrAccessDenied);

    //-- 4.2 "unfinalise" the volume, it still shall remain RO
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EForceUnfinalise);
    test_KErrNone(nRes);

    //-- try to write a file on RO volume; it shall fail with KErrAccessDenied
    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test(nRes == KErrAccessDenied);
    file.Close();

    //-- remount FS, the drive shall become RW
    nRes = RemountFS(TheFs, gDriveNum);
    test_KErrNone(nRes);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //-- try to write a file on RW volume, shall be OK
    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test(nRes == KErrNone);
    file.Close();

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(!bDriveFinalised);

    //============= 5. test various finalisation modes

    //-- 5.1  Not finalised -> EFinal_RW (KErrNone)
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test(nRes == KErrNone);
    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //-- 5.2  EFinal_RW -> EFinal_RO (KErrNone)
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RO);
    test(nRes == KErrNone);
    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //-- 5.2  EFinal_RO -> EFinal_RW  (KErrAccessDenied)
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test(nRes == KErrAccessDenied);
    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    //-- 5.3 restore
    nRes = RemountFS(TheFs, gDriveNum);
    test_KErrNone(nRes);


    //============= 6. test old RFs::FinaliseDrives API

    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test(nRes == KErrNone);

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(!bDriveFinalised);

    TheFs.FinaliseDrives(); //-- shall work as TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW) but for ALL drives

    bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    nRes = CreateEmptyFile(TheFs, KFileName, 128000);
    test(nRes == KErrNone);

}

//----------------------------------------------------------------------------------------------
//! @SYMTestCaseID      PBASE-T_MOUNT-0523
//! @SYMTestType        UT
//! @SYMPREQ            PREQ1721
//! @SYMTestCaseDesc    testing boot and backup boot sectors on FAT32 volume
//!
//! @SYMTestActions
//!             0   Quick format the drive
//!             1   read main and backup boot & fsinfo sectors and check their validity
//!             2   corrupt main boot sector and check that the drive can be mounted (backup boot sector is used)
//!             3   corrupt backup boot sector and check that the drive can not be mounted.
//!             4   Quick format the drive to restore test environment
//!
//! @SYMTestExpectedResults finishes if the boot sector and backup boot sector functionality compliant with the FAT specs. panics otherwise
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented
//----------------------------------------------------------------------------------------------
static void TestBackupBootSector()
{
    test.Next(_L("Testing Backup Boot Sector.\n"));

    TInt nRes;

    //-- quick format the drive
    FormatVolume(ETrue);

    if(!Is_Fat32(TheFs, gDriveNum))
    {
        test.Printf(_L("This step requires FAT32 ! Skipping.\n"));
        return;
    }

    TFatBootSector  mainBootSec, backupBootSec;
    TFSInfo         mainFSInfo, backupFSInfo;

    const TUint32 posMainBootSec = KBootSectorNum << KDefaultSectorLog2;
    const TUint32 posBkBootSec   = KBkBootSectorNum << KDefaultSectorLog2;

    //-- read main and backup boot & fsinfo sectors and check their validity
    nRes = ReadBootSector(TheFs, gDriveNum, posMainBootSec, mainBootSec);
    test_KErrNone(nRes);

    //-- backup boot sector # must be 6
    nRes = ReadBootSector(TheFs, gDriveNum, posBkBootSec, backupBootSec);
    test_KErrNone(nRes);

    test(mainBootSec.IsValid());
    test(backupBootSec.IsValid());
    test(mainBootSec == backupBootSec);

    //-- read fsinfo sectors
    const TUint32 posMainFSInfo = mainBootSec.FSInfoSectorNum() << KDefaultSectorLog2;
    const TUint32 posBkFSInfo   = (KBkBootSectorNum + mainBootSec.FSInfoSectorNum()) << KDefaultSectorLog2;

    test(posMainFSInfo != 0);
    test(posBkFSInfo != 0);

    nRes = ReadFSInfoSector(TheFs, gDriveNum, posMainFSInfo, mainFSInfo);
    test_KErrNone(nRes);

    nRes = ReadFSInfoSector(TheFs, gDriveNum, posBkFSInfo, backupFSInfo);
    test_KErrNone(nRes);

    test(mainFSInfo.IsValid());
    test(backupFSInfo.IsValid());
    test(mainFSInfo == backupFSInfo);

    //-- corrupt main boot sector and check that the drive can be mounted
    test.Printf(_L("Corrupting main boot sector...\n"));


    //-- A1. corrupt main boot sector starting from the pos:0
    nRes = FillMedia(TheFs, gDriveNum, posMainBootSec, posMainBootSec+KDefaultSectorSize, 0xaa);

    //-- A2. remount FS, it shall be OK because of the using backup boot sector
    nRes = RemountFS(TheFs, gDriveNum);
    test_KErrNone(nRes);


    //-- B1. corrupt BACKUP boot sector starting from the sec:6
    nRes = FillMedia(TheFs, gDriveNum, posBkBootSec, posBkBootSec+KDefaultSectorSize, 0xbb);

    //-- B2. remount FS, unable to mount.
    nRes = RemountFS(TheFs, gDriveNum);
    test(nRes == KErrCorrupt);


    //-- quick format the drive
    FormatVolume(ETrue);

}

//----------------------------------------------------------------------------------------------
//! @SYMTestCaseID      PBASE-T_MOUNT-0524
//! @SYMTestType        UT
//! @SYMPREQ            PREQ1721
//! @SYMTestCaseDesc    testing FSInfo sector functionality.
//!
//! @SYMTestActions
//!             0   Quick format the drive
//!             1   finalise the drive to write correct data to the FSInfo & its backup copy.
//!             2   read FSInfo sector, its backup copy and check that they are both valid, identical and contain correct data
//!             3   check that after the formatting FS info values are the same as real, obtained from the FAT scanning.
//!             4   create a random - sized file, check that after finalisation the number of free clusters is identical to the FSinfo
//!             5   Quick format the drive to restore test environment
//!
//! @SYMTestExpectedResults finishes if the FSInfo sectors functionality compliant with the FAT specs. panics otherwise
//! @SYMTestPriority        High
//! @SYMTestStatus          Implemented
//----------------------------------------------------------------------------------------------
static void TestFSInfoSector()
{
    test.Next(_L("Testing FSInfo Sector.\n"));

#ifndef _DEBUG
    test.Printf(_L("Skipping the test in the Release build! \n"));
    return;
#else

    TInt nRes;

    //-- quick format the drive
    FormatVolume(ETrue);

    if(!Is_Fat32(TheFs, gDriveNum))
    {
        test.Printf(_L("This step requires FAT32 ! Skipping.\n"));
        return;
    }

    TFatBootSector  bootSec;
    const TUint32 posMainBootSec = KBootSectorNum << KDefaultSectorLog2;
    nRes = ReadBootSector(TheFs, gDriveNum, posMainBootSec, bootSec);
    test_KErrNone(nRes);

    const TUint32 bytesPerSector = bootSec.BytesPerSector();
    const TUint32 secPerClust = bootSec.SectorsPerCluster();

    //-- finalise the drive, just in case
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test_KErrNone(nRes);

    //============= 1. read FSInfo sector and its backup copy and compare them
    TFSInfo fsInfoSec;

    //-- main FSInfo
    nRes = ReadFSInfoSector(TheFs, gDriveNum, KFSInfoSectorNum*bytesPerSector, fsInfoSec);
    test_KErrNone(nRes);
    test(fsInfoSec.IsValid());

    TUint32 freeClusters_FSInfo = fsInfoSec.FreeClusterCount();
    TUint32 nextFree_FSInfo = fsInfoSec.NextFreeCluster();

    //-- backup FSInfo
    nRes = ReadFSInfoSector(TheFs, gDriveNum, KBkFSInfoSectorNum*bytesPerSector, fsInfoSec);
    test_KErrNone(nRes);
    test(fsInfoSec.IsValid());

    //-- both copies must be identical
    test(freeClusters_FSInfo == fsInfoSec.FreeClusterCount());
    test(nextFree_FSInfo == fsInfoSec.NextFreeCluster());

    //-- FAT[0] and FAT[1] are not used; FAT[2] is taken by the 1st cluster of the FAT32 Root directory.
    test(nextFree_FSInfo == (2+1));

    //============= 2. check that after the formatting FS info values are the same as real.

    //-- 2.1 disable using FSInfo and other stuff
    SetFsyDebugFlag(gDriveNum, KMntProp_Disable_FsInfo);

    //-- remount FAT; using FSInfo is disabled. FAT will be explicitly scanned.
    nRes = RemountFS(TheFs, gDriveNum);
    test_KErrNone(nRes);

    //-- restore mounting mechanism
    SetFsyDebugFlag(gDriveNum, KMntProp_EnableALL);

    //-- get free clusters number from the FSY, which in turn had counted them explicitly
    TVolumeInfo volInfo;
    nRes = TheFs.Volume(volInfo, gDriveNum);
    test_KErrNone(nRes);

    TUint32 freeClusters = (TUint32)(volInfo.iFree / (bytesPerSector * secPerClust));
    test(freeClusters == freeClusters_FSInfo);

    //============= 3. create a random - sized file, check that after finalisation the number of free clusters is identical to the FSinfo
    _LIT(KFileName, "\\FILE1.DAT");
    const TUint32 rndClusters = 7+((TUint32)Math::Rand(gRndSeed)) % 5000;
    const TUint32 fileSz = rndClusters * bytesPerSector * secPerClust;
    nRes = CreateEmptyFile(TheFs, KFileName, fileSz);
    test_KErrNone(nRes);

    //-- 3.1 get data from FS
    nRes = TheFs.Volume(volInfo, gDriveNum);
    test_KErrNone(nRes);
    freeClusters = (TUint32)(volInfo.iFree / (bytesPerSector * secPerClust));

    //-- 3.2 finalise the volume and get data from FSInfo
    nRes =TheFs.FinaliseDrive(gDriveNum, RFs::EFinal_RW);
    test_KErrNone(nRes);

    //-- main FSInfo
    nRes = ReadFSInfoSector(TheFs, gDriveNum, KFSInfoSectorNum*bytesPerSector, fsInfoSec);
    test_KErrNone(nRes);
    test(fsInfoSec.IsValid());

    freeClusters_FSInfo = fsInfoSec.FreeClusterCount();
    nextFree_FSInfo = fsInfoSec.NextFreeCluster();

    //-- backup FSInfo
    nRes = ReadFSInfoSector(TheFs, gDriveNum, KBkFSInfoSectorNum*bytesPerSector, fsInfoSec);
    test_KErrNone(nRes);
    test(fsInfoSec.IsValid());

    //-- both copies must be identical
    test(freeClusters_FSInfo == fsInfoSec.FreeClusterCount());
    test(nextFree_FSInfo == fsInfoSec.NextFreeCluster());

    //-- the information in FSInfo must be the same as in FAT
    test(freeClusters == freeClusters_FSInfo);

    TBool bDriveFinalised = DoCheckVolumeFinalised(gDriveNum);
    test(bDriveFinalised);

    TheFs.Delete(KFileName);

    //-- restore mounting mechanism
    SetFsyDebugFlag(gDriveNum, KMntProp_EnableALL);

#endif //_DEBUG
}

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

/** initialise test global objects */
static void InitGlobals()
{
    TInt nRes;

    //-- define a propery which will control mount process in the fsy.
    //-- The property key is a drive number being tested

    _LIT_SECURITY_POLICY_PASS(KTestPropPolicy);
    nRes = RProperty::Define(KThisTestSID, gDriveNum, RProperty::EInt, KTestPropPolicy, KTestPropPolicy);
    test(nRes == KErrNone || nRes == KErrAlreadyExists);

    nRes = RProperty::Set(KThisTestSID, gDriveNum, KMntProp_EnableALL);
    test_KErrNone(nRes);

    gRndSeed = Math::Random();
    (void)&gRndSeed; //-- get rid of warning
}

/** destroy test global objects */
static void DestroyGlobals()
{
    //-- delete test property
    RProperty::Delete(KThisTestSID, gDriveNum);

    TVolumeInfo v;
    TheFs.Volume(v);
}

//-------------------------------------------------------------------
/**
    Manual test. Requires manual removing and putting back the media.
    On the emulator one can use pressing (and holding) F5 key to simulate media removal.
*/
void Manual_TestRemount_On_MediaRemoval()
{
    TInt nRes;

    _LIT(KFileName, "\\my_file1.dat");
    const TUint32 KFileSz = K1MegaByte;

    //-- 1. create a file
    nRes = CreateEmptyFile(TheFs, KFileName, KFileSz);
    test_KErrNone(nRes);

    RFile file;

    nRes = file.Open(TheFs, KFileName, EFileRead | EFileWrite);
    test_KErrNone(nRes);

    TBuf8<512> buf(512);
    TVolumeInfo vi;
    buf.FillZ();

    TKeyCode key;

    for(;;)
    {
        TheFs.SetDebugRegister(0x00);

        nRes = file.Read(0, buf);

        test.Printf(_L("Remove the media and press a key.\n"));
        key = test.Getch();
        if(key == EKeyEscape)
                break;

        TheFs.SetDebugRegister(KFSYS | KFSERV);
        nRes = file.Read(0, buf);

        if(nRes != KErrNone)
        {

            test.Printf(_L("ReadFile: %d!\n"), nRes);

            key = test.Getch();
            if(key == EKeyEscape)
                break;

            nRes = TheFs.Volume(vi,gDriveNum);
            test.Printf(_L("Volume: %d!\n"), nRes);

            key = test.Getch();
            if(key == EKeyEscape)
                break;

            nRes = file.Write(0, buf);
            test.Printf(_L("WriteFile: %d!\n"), nRes);

            key = test.Getch();
            if(key == EKeyEscape)
                break;

        }

    }


    file.Close();
}


//-------------------------------------------------------------------
/**
    Wait for the request aRqStat to be completed with timeout.

    @param  aRqStat         request status object we need to wait to complete
    @param  aTimeout_uS     timeout in microseconds

    @return ETrue   if the aRqStat is completed before time is out
            EFalse  if aTimeout_uS has passed. And the state of the aRqStat not changed.
*/
TBool WaitForRequestWithTimeout(TRequestStatus& aRqStat, TUint32 aTimeout_uS)
{
    TRequestStatus  rqStatTimeout(KRequestPending);
    RTimer          tmrTimeOut;
    TInt            nRes;
    TBool           bReqCompleted;

    if(aRqStat.Int() != KRequestPending)
        return ETrue; //-- nothing to wait for.

    //-- set up a timeout timer
    nRes = tmrTimeOut.CreateLocal();
    test(nRes == KErrNone);

    tmrTimeOut.After(rqStatTimeout, aTimeout_uS);

    User::WaitForRequest(aRqStat, rqStatTimeout);

    if(aRqStat == KRequestPending)
        {//-- timeout.
        bReqCompleted = EFalse;
        }
        else
        {//-- the main request has been completed, cancel timer
            bReqCompleted = ETrue;
            if(rqStatTimeout.Int() == KRequestPending)
                {
                tmrTimeOut.Cancel();
                User::WaitForRequest(rqStatTimeout);
                }
        }

    tmrTimeOut.Close();

    return bReqCompleted;
}

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

void TestNotifyDiskSpace()
{
    test.Next(_L("Testing NotifyDiskSpace() on asynchronous mounting\n"));

#ifndef _DEBUG
    test.Printf(_L("Skipping the test in the Release build! \n"));
    return;
#else

    TInt nRes;
    TRequestStatus rqStat;
    TVolumeInfo volInfo;

    //-- quick format the drive
    FormatVolume(ETrue);

    if(!Is_Fat32(TheFs, gDriveNum))
    {//-- only FAT32 supports asynch mounting;
        test.Printf(_L("This test step requires FAT32!\n"));
        return;
    }

    //-- FAT32 free space threshold that is supposed to be triggered by notifiers
    const TInt64 KFreeSpaceThreshold = 300*K1MegaByte;

    //-- Turn OFF using FSInfo and ON FAT32 background scanning; it will cause compulsory FAT32 background free clusters scan.
    //-- if FAT32 background free clusters scan is disabled in config, this test can hang on waiting for free space notifications.
    test.Printf(_L("==== Enabled FAT32 BkGnd scan, FSInfo disabled ====\n"));
    SetFsyDebugFlag(gDriveNum, KMntProp_DisableALL & ~KMntProp_Disable_FatBkGndScan);


    //===== create a big file in the beginning in order to avoid freeSpaceThreshold being reached too fast
    nRes = CreateEmptyFile(TheFs, _L("\\big_empty_file.bin"), 10*K1MegaByte);
    test_KErrNone(nRes);

//TheFs.SetDebugRegister(0x03);

    //===== Test that FAT32 free space scanning thread updates File Server free space notifiers
    test.Printf(_L("Testing FAT32 free space scanning thread triggers notifiers..."));

    nRes = RemountFS(TheFs, gDriveNum); //-- remount FS; it must cause FAT32 free space scan in background
    test_KErrNone(nRes);

    test.Printf(_L("Waiting for %LU bytes available on the volume...\n"), KFreeSpaceThreshold);

    //-- check if we can use the notifiers at all... If free space is >= KFreeSpaceThreshold, the notifier won't be triggered
    //-- get _current_ amount of free space asynchronously
    TheFs.Volume(volInfo, gDriveNum, rqStat);
    User::WaitForRequest(rqStat);
    test(rqStat.Int()==KErrNone);

    if(volInfo.iSize <= KFreeSpaceThreshold)
    {
    test.Printf(_L("The volume is too small for %LU bytes notify ...\n"), KFreeSpaceThreshold);
    test.Printf(_L("The test is inconclusive ...\n"));
    return;
    }

    if(volInfo.iFree >= KFreeSpaceThreshold)
    {
    test.Printf(_L("The volume already has %LU free bytes...\n"), volInfo.iFree);
    test.Printf(_L("The test is inconclusive ...\n"));
    return;
    }


    TheFs.NotifyDiskSpace(KFreeSpaceThreshold, gDriveNum, rqStat);
    test(rqStat.Int()==KRequestPending);

    //-- wait for notification for 30 seconds; If for some reason FAT32 background scanning for free clusters doesn't
    //-- work (e.g. configued out in FAT), this test is inconclusive.
    TBool bCompleted = WaitForRequestWithTimeout(rqStat, 30*K1Sec);

    if(!bCompleted)
    {
        test.Printf(_L("Wait timeout! something is wrong...\n"));
        test(0);
    }

    test_KErrNone(rqStat.Int());

    //-- get _current_ amount of free space asynchronously
    TheFs.Volume(volInfo, gDriveNum, rqStat);
    User::WaitForRequest(rqStat);
    test(rqStat.Int()==KErrNone);
    test.Printf(_L("Current amount of free space on the volume: %LU \n"), volInfo.iFree);


    //===== Test that aborting FAT32 free space scanning thread will trigger notifiers
    test.Printf(_L("Testing aborting FAT32 free space scanning thread..."));

    nRes = RemountFS(TheFs, gDriveNum); //-- remount FS; it must cause FAT32 free space scan in background
    test_KErrNone(nRes);

    TheFs.NotifyDiskSpace(KFreeSpaceThreshold, gDriveNum, rqStat);
    test(rqStat.Int()==KRequestPending);

    nRes = RemountFS(TheFs, gDriveNum); //-- remount FS; it will abort the scanning thread
    test_KErrNone(nRes);

    test(rqStat.Int() != KRequestPending);

    //-- get _current_ amount of free space asynchronously
    TheFs.Volume(volInfo, gDriveNum, rqStat);
    User::WaitForRequest(rqStat);
    test(rqStat.Int()==KErrNone);
    test.Printf(_L("Current amount of free space on the volume: %LU \n"), volInfo.iFree);


    //-- find out free space on the volume; it will also blocks until FAT32 free space scanning finishes.
    nRes = TheFs.Volume(volInfo);
    test(nRes==KErrNone);
    test.Printf(_L("free space on the volume: %LU \n"), volInfo.iFree);


//TheFs.SetDebugRegister(0x00);

    //-- restore mounting mechanism
    SetFsyDebugFlag(gDriveNum, KMntProp_EnableALL);

#endif  //_DEBUG
}

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

void CallTestsL()
    {
    //-- set up console output
    Fat_Test_Utils::SetConsole(test.Console());

    TInt nRes=TheFs.CharToDrive(gDriveToTest, gDriveNum);
    test(nRes==KErrNone);

    //-- check if this is FAT
    if(!Is_Fat(TheFs, gDriveNum) || Is_Automounter(TheFs, gDriveNum))
    {//-- it doesn't make much sense to run this test under automounter+FAT. The automounter can't easily handle formatting corrupted media
     //-- and the mounting permaormance measurements don't make much sense in this case as well.

        test.Printf(_L("Skipping. This test requires explicitly mounted FAT file system.\n"));
        return;
    }

    //-- check this is not the internal ram drive
    TVolumeInfo v;

    nRes = TheFs.Volume(v);
    test(nRes==KErrNone);
    if(v.iDrive.iMediaAtt & KMediaAttVariableSize)
        {
        test.Printf(_L("Skipping. Internal ram drive not tested.\n"));
        return;
        }

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

    PrintDrvInfo(TheFs, gDriveNum);
    InitGlobals();


    TestNotifyDiskSpace();

    //-------------------------------------
    TestBackupBootSector();
    TestFSInfoSector();
    TestFinaliseFS();

    //-------------------------------------
    TestFAT_Mounting_Performance();
    //-------------------------------------
    //-- manual test
    //Manual_TestRemount_On_MediaRemoval();

    //-------------------------------------
    DestroyGlobals();

    }


















