// Copyright (c) 2004-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:
// Component test of registration and deregistration
// 1. Test registering MS FS
// Test actions:
// a. Load MS Server/File system and start it. 
// b. Select a drive, say, Y, format it using FAT FS
// c. Read and store the boot sector of Y
// d. Unmount FAT FS from Y
// e. Mount MS FS on Y
// f. Read the boot sector of Y by passing SCSI command through TestLdd 
// g. Compare the two boot sectors, which are obtained under FAT MS and MS FS repectively
// h. Try to read Y (assuming it's a removable media) using RFs API.
// Expected results:
// This test should be run for different drives.
// Mounting non-removable media (step b) should fail.
// For removable media: steps from c to g should complete successfully
// Read boot sector should be the same as the saved. 
// Step h should fail (KErrNotSupported).
// 2. Test deregistering MS FS
// Test actions:
// a. Try to unmount MS FS from Y while SCSI is writing to it
// b. Wait for access completion.
// c. Unmount MS FS from Y. 	
// d. Try to read from Y using RFs API. 
// e. Remount FAT FS on Y.
// f. Issue UnitReady and Read SCSI command through test LDD. 
// g. Try to read from Y using RFs API.
// h. Unmount FAT FS from Y and mount MS FS on it
// Expected results:
// Deregister request (step a) should return an appropriate error code when media is in use. 
// Next deregistering (step c) should be successful. 
// Attempt (step d) should fail. 
// SCSI commands (step f) should report error when MS FS is unmounted. 
// File server read request (step g) should be successful when FAT FS is mounted  
// 
//

/**
 @file
 @internalTechnology
*/

#define __E32TEST_EXTENSION__
#include <f32file.h>
#include <e32test.h>
#include <e32std.h>
#include <e32svr.h>
#include <hal.h>
#include <massstorage.h>
#include "t_ms_main.h"
#include "testusbc.h"

#define CBW_LENGTH          31
#define CSW_LENGTH          13
#define SCSI_READ_LEN       10
#define SCSI_WRITE_LEN      10
#define SCSI_UNIT_READY_LEN 6
#define SCSI_MED_RMVL_LEN   6
#define BOOTSECTOR_SIZE     512

_LIT(KMsFsyName, "MassStorageFileSystem");

const TUint KMBRFirstPartitionSectorOffset = 0x1BE + 8;
const TUint KBootRecordSignature = 0xAA55;

LOCAL_D TChar driveLetter[2];
LOCAL_D TInt nonRemovalDrvNo;
LOCAL_D TInt removalDrvNo;
LOCAL_D TUint8 testLun(0);                // Use MMC card for testing

#define LOG_AND_TEST(a, e) {if (a!=e) {test.Printf(_L("lvalue %d, rvalue%d\n\r"), a,e); test(EFalse);}}
LOCAL_C void ParseCommandArguments()
//
// Parses the command line arguments
// We expect 3 parameters:
//	- Drive letter for non-removable drive
//	- Drive letter for removable drive
//	- LUN for removable drive (0-7)
//
	{
	TBuf<0x100> cmd;
	User::CommandLine(cmd);
	TLex lex(cmd);
	TPtrC token;
	TBool cmdOk= ETrue;
	for (int i = 0; i < 3; i++)  //
		{
		token.Set(lex.NextToken());
		if (token.Length() != 0)
			{
			if (i <2)
				{
				driveLetter[i] = token[0];
				driveLetter[i].UpperCase();
				test.Printf(_L("CmdLine Param=%S\r\n"),&token);
				}
			else
				{
				TChar cLun = token[0];
				TInt lun = TInt(cLun) - 0x30;
				if (lun>=0 && lun <8)
					{
					testLun = TUint8(lun);
					}
				else
					{
					cmdOk= EFalse;
					}
				}
			}
		else 
			{
			cmdOk= EFalse;
			break;
			}
		}
		

		if (!cmdOk) 
			{
			test.Printf(_L("No or not enough command line arguments - using default arguments\r\n"));
			// code drive letters based on platform
			TInt uid;
			TInt r=HAL::Get(HAL::EMachineUid,uid);
			LOG_AND_TEST(r,KErrNone);
    	
			switch(uid)
	   			{
	   			case HAL::EMachineUid_Lubbock:
	   				driveLetter[0] = 'C';
	   				driveLetter[1] = 'F';
	   				test.Printf(_L("Test is running on Lubbock\r\n"));
					testLun = 2;
  					break;
	   			case HAL::EMachineUid_Win32Emulator:
	   				driveLetter[0] = 'Y';
	   				driveLetter[1] = 'X';
	   				test.Printf(_L("Test is running on Win32 Emulator\r\n"));
					testLun = 1;
	   				break;
	   			case HAL::EMachineUid_OmapH4:
	   				driveLetter[0] = 'C';
	   				driveLetter[1] = 'D';
	   				test.Printf(_L("Test is running on H4 board\r\n"));
					testLun = 0;
	   				break;
	   			default:
					// Assume it's a H2 board for now as no relevant Enum is found
	   				driveLetter[0] = 'C';
	   				driveLetter[1] = 'D';
	   				test.Printf(_L("Test is running on H2 board\r\n"));	
	   				testLun = 0;				
	   				break;
	   			}
	   			
				test.Printf(_L("Parameters used for test:\r\n\tfixed drive\t\t%c\r\n\tremovable drive\t\t%c\r\n\tLUN\t\t\t%d\r\n"), 
		        		    (TUint) driveLetter[0], (TUint) driveLetter[1], testLun);
			}	   		
	}

LOCAL_C void fillInt(TUint8* dest, TInt source)
    // 
    // Copy an int. Little endian
    //
    {
    for (TInt i = 0; i < 4; i++)
        {
        *dest++ = TUint8((source >> i*8) & 0xFF);
        }
    }

LOCAL_C TInt extractInt(const TUint8* aBuf)
    //
    // Extract an integer from a buffer. Assume little endian
    //
    {
    return aBuf[0] + (aBuf[1] << 8) + (aBuf[2] << 16) + (aBuf[3] << 24);
    }

LOCAL_C void createReadCmd(TDes8& aRead10Buf, TInt aLogicalBlkAddr, TInt aTotalBlocks)
    // 
    // Prepare SCSI read(10) command
    //
    {
    // Zero out the whole buffer
    aRead10Buf.FillZ(SCSI_READ_LEN);
    // operation code
    aRead10Buf[0] = 0x28;    
    // Fill in logical block address. Big endian
    aRead10Buf[2] = TUint8((aLogicalBlkAddr >> 24) & 0xFF);
    aRead10Buf[3] = TUint8((aLogicalBlkAddr >> 16) & 0xFF);
    aRead10Buf[4] = TUint8((aLogicalBlkAddr >> 8) & 0xFF);
    aRead10Buf[5] = TUint8(aLogicalBlkAddr & 0xFF);
    
    // Data transfer length (# of sectors). Big endian
    aRead10Buf[7] = TUint8((aTotalBlocks >> 8) & 0xFF);
    aRead10Buf[8] = TUint8((aTotalBlocks & 0xFF));
    }

LOCAL_C void createCBW(TDes8& aCbw, TInt aDCBWTag, TInt aDataTransferLen, TUint8 aInOutFlag, TUint8 aCBLength, TDes8& aCBWCB)
    // 
    // aCbw:            stores CBW
    // aDCBWTag:        a command block tag sent by the host. Used to associates a CSW
    //                  with corresponding CBW
    // aDataTranferLen: the number of bytes the host expects to transfer
    // aInOutFlag:      value for bmCBWFlags field, indicating the direction of transfer
    // aCBLengh:        valid length of CBWCB field in bytes
    // aCBWCB			the actual command to be wrapped
    {
    // Zero out aCbw
    aCbw.FillZ(CBW_LENGTH);

    // dCBWSignature field, the value comes from spec
    TInt dCBWSignature = 0x43425355;
    fillInt(&aCbw[0], dCBWSignature);
    // dCBWTag field
    fillInt(&aCbw[4], aDCBWTag);
    // dCBWDataTransferLength field
    fillInt(&aCbw[8], aDataTransferLen);
    // bmCBWFlags field
    aCbw[12] = aInOutFlag;
	aCbw[13] = testLun;
    // bCBWCBLength field
    aCbw[14] = aCBLength;          

    // CBWCB field
    for (TInt i = 0; i < aCBLength; ++i)
    	{
    	aCbw[15 + i] = aCBWCB[i];
    	}
    }

LOCAL_C void doComponentTest()
    //
    // Do the component test
    //
	{
	__UHEAP_MARK;	

    TInt ret;
    test.Next(_L("Start MountStart test. Be sure MMC card is inserted."));

    // Connect to the server
    RFs fs;
    LOG_AND_TEST(KErrNone,  fs.Connect());
    
    // Convert drive letters to their numerical equivalent
	ret = fs.CharToDrive(driveLetter[0],nonRemovalDrvNo);
	LOG_AND_TEST(ret, KErrNone);
	ret = fs.CharToDrive(driveLetter[1],removalDrvNo);
	LOG_AND_TEST(ret,  KErrNone);	


	// Load the logical device
	_LIT(KDriverFileName,"TESTUSBC.LDD");
	ret = User::LoadLogicalDevice(KDriverFileName);
	test(ret == KErrNone || ret == KErrAlreadyExists);

    // Add MS file system
	_LIT(KMsFs, "MSFS.FSY");
	ret = fs.AddFileSystem(KMsFs);
	test(ret == KErrNone || ret == KErrAlreadyExists);
	
	// DEF080979: RFs::AddFileSystem, wrong error code when re-adding 
	// the mass storage file system. Confirm that RFs::AddFileSystem 
	// returns the correct error code if the file system already exists. 
	// Also confirm that the mass storage file system is usable after such
	// a failed attempt to re-add the file system.
	ret = fs.AddFileSystem(KMsFs);
	test(ret == KErrAlreadyExists);

    // Start Ms file system
    RUsbMassStorage usbMs;

    TMassStorageConfig config;
    
    config.iVendorId.Copy(_L("vendorId"));
    config.iProductId.Copy(_L("productId"));
    config.iProductRev.Copy(_L("rev"));


	ret = usbMs.Connect();
    LOG_AND_TEST(KErrNone, ret);
  
    // Start usb mass storage device
    LOG_AND_TEST(KErrNone , usbMs.Start(config));

	TBuf<128> fsName;
		
#if defined(__WINS__)  //we have no "free" non-removable drive at hardware to run this,
			  
    // Get the file system name on non-removable drive

	LOG_AND_TEST(KErrNone ,fs.FileSystemName(fsName, nonRemovalDrvNo));

    // Mount MS FS on to non-removable drive. This should fail
    test(KErrNone != fs.MountFileSystem(KMsFsyName, nonRemovalDrvNo));

    // Unmount MS FS from non-removable drive
	LOG_AND_TEST(KErrNone, fs.DismountFileSystem(KMsFsyName, nonRemovalDrvNo));

    // Mount FAT FS on to drive (restoring)
    LOG_AND_TEST(KErrNone,  fs.MountFileSystem(fsName, nonRemovalDrvNo));
#endif //WINS

    // Format removable drive using FAT FS
    RFormat format;
    
    TBuf<2> removalDrive;
    removalDrive.Append(driveLetter[1]);
    removalDrive.Append(':');
    TInt tracksRemaining;
    test.Next(_L("Start MMC card formatting"));
    LOG_AND_TEST(KErrNone, format.Open(fs, removalDrive, EHighDensity || EQuickFormat, tracksRemaining));
    while (tracksRemaining)
        {
        test.Printf(_L("."));
        LOG_AND_TEST(KErrNone,  format.Next(tracksRemaining));
        }
    format.Close();
  	test.Printf(_L("\nDone!\n"));
    // Get the boot sector info using FAT FS and save it for later comparison
    TBuf8<512> fatBootSector;
    RRawDisk fatDiskF;
    LOG_AND_TEST(KErrNone, fatDiskF.Open(fs, removalDrvNo));
    LOG_AND_TEST(KErrNone, fatDiskF.Read(0, fatBootSector));
	fatDiskF.Close();    

	LOG_AND_TEST(KErrNone, fs.FileSystemName(fsName, removalDrvNo)); 

	// Set up sessions for dismount API tests

	const TInt KNumClients = 10;
    RFs clientFs[KNumClients];
    RFs dismountFs1;
	TRequestStatus trsClientNotify[KNumClients];
	TRequestStatus trsClientComplete;
	TRequestStatus trsClientComplete1;

	LOG_AND_TEST(KErrNone, dismountFs1.Connect());

	TInt i = 0;
	for(i=0; i< KNumClients; i++)
		{
		LOG_AND_TEST(KErrNone, clientFs[i].Connect());
		}

	// Test invalid mode argument to RFs::NotifyDismount

    test.Next(_L("Test invalid mode argument to RFs::NotifyDismount"));
	for(i=0; i< KNumClients; i++)
		{
   		clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i], (TNotifyDismountMode)0xFEEDFACE);
		test(trsClientNotify[i] == KErrArgument);
		}

	// Register for notification of pending media removal and check status

    test.Next(_L("Register for notification of pending media removal and check status"));
	for(i=0; i< KNumClients; i++)
		{
   		clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i]);
		test(trsClientNotify[i] == KRequestPending);
		}

	// Notify clients of pending media removal and check status

    test.Next(_L("Notify clients of pending media removal and check status"));
   	fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
	test(trsClientComplete == KRequestPending);

	// Check that client has notification of pending media removal

    test.Next(_L("Check that client has notification of pending media removal"));
	for(i=0; i< KNumClients; i++)
		{
		test(trsClientNotify[i] == KErrNone);
		}

	// Respond to the dismount using RFs::AllowDismount (only 2 clients)

	LOG_AND_TEST(KErrNone, clientFs[0].AllowDismount(removalDrvNo));
	test(trsClientComplete == KRequestPending);
	LOG_AND_TEST(KErrNone, clientFs[1].AllowDismount(removalDrvNo));
	test(trsClientComplete == KRequestPending);

	// Check that file system can't be dismounted as all clients haven't responded

	test.Next(_L("Check that file system can't be dismounted as all clients haven't responded"));
	LOG_AND_TEST(KErrInUse, fs.DismountFileSystem(fsName, removalDrvNo));

	// Before all clients have completed, cancel the dismount

	test.Next(_L("Before all clients have completed, cancel the dismount"));
	fs.NotifyDismountCancel(trsClientComplete);
	test(trsClientComplete == KErrCancel);

	for(i=2; i< KNumClients; i++)
		{
		LOG_AND_TEST(KErrNone,     clientFs[i].AllowDismount(removalDrvNo));
		LOG_AND_TEST(KErrNotFound, clientFs[i].AllowDismount(removalDrvNo));
		test(trsClientComplete == KErrCancel);
		}

	// Check that file system can be dismounted after cancelling async dismount

	test.Next(_L("Check that file system can be dismounted after cancelling async dismount"));
	LOG_AND_TEST(KErrNone, fs.DismountFileSystem(fsName, removalDrvNo));

	// ...remount FAT

	test.Next(_L("Mount FAT FS on to the removal drive"));
	LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

	for(i=0; i< KNumClients; i++)
	    clientFs[i].Close();

    dismountFs1.Close();

	//
	// Test dismounting while resourses are open
	//
	
	_LIT(KFileName, ":\\foo");
	TBuf<7> fileName;
	fileName.Append(driveLetter[1]);
	fileName.Append(KFileName);

	RFile file;
	test.Next(_L("Attempting to open a file\n\r"));
	ret = file.Create(fs, fileName, EFileRead | EFileReadDirectIO | EFileWriteDirectIO);
	test(ret == KErrNone || ret == KErrAlreadyExists);
	LOG_AND_TEST(KErrNone, file.Write(0,_L8("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")));
	file.Close();

	TBuf<7> dirName;
	dirName.Append(driveLetter[1]);
	dirName.Append(KFileName);

	RDir dir;
	TEntry dirEntry;
	test.Next(_L("Attempting to open a directory\n\r"));
    LOG_AND_TEST(KErrNone, dir.Open(fs, dirName, KEntryAttNormal));
    LOG_AND_TEST(KErrNone, dir.Read(dirEntry));
	dir.Close();

	fs.Close();

	TInt pass;
	for(pass=0; pass<5; pass++)
	{
		LOG_AND_TEST(KErrNone, fs.Connect());
		LOG_AND_TEST(KErrNone, dismountFs1.Connect());

		TInt i = 0;
		for(i=0; i< KNumClients; i++)
			{
			LOG_AND_TEST(KErrNone, clientFs[i].Connect());
			}

		// Open a file on the removable drive
		
		RFile file;
		LOG_AND_TEST(KErrNone, file.Open(fs, fileName, EFileRead | EFileReadDirectIO | EFileWriteDirectIO));
		TBuf8<0x40> buf1;
		LOG_AND_TEST(KErrNone, file.Read(0, buf1));
		LOG_AND_TEST(36, buf1.Length());
		test(buf1 == _L8("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));

		// Unmount FAT FS from the removable drive - this should fail with a file open
		
		LOG_AND_TEST(KErrInUse, fs.DismountFileSystem(fsName, removalDrvNo));

		// Open a directory  on the removable drive

		test.Next(_L("Attempting to open a directory\n\r"));
		LOG_AND_TEST(KErrNone, dir.Open(fs, dirName, KEntryAttNormal));
		LOG_AND_TEST(KErrNone, dir.Read(dirEntry));

		// Check simple client dismount notification and cancel (before issuing a dismount request)

		test.Next(_L("Register for notification of pending media removal and check status"));
		for(i=0; i< KNumClients; i++)
			{
   			clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Cancel notification of pending media removal and check status"));
		for(i=0; i< KNumClients; i++)
			{
	   		clientFs[i].NotifyDismountCancel(trsClientNotify[i]);
			test(trsClientNotify[i] == KErrCancel);
			}

		// Check issuing and cancelling a dismount request while clients are responding

		test.Next(_L("Register for notification of pending media removal (again) and check status"));
		for(i=0; i< KNumClients; i++)
			{
   			clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Notify clients of pending media removal and check status"));
   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
		test(trsClientComplete == KRequestPending);

		test.Next(_L("Check that client has notification of pending media removal"));
		for(i=0; i< KNumClients; i++)
			{
			test(trsClientNotify[i] == KErrNone);
			}

		LOG_AND_TEST(KErrNone, clientFs[0].AllowDismount(removalDrvNo));
		test(trsClientComplete == KRequestPending);
		LOG_AND_TEST(KErrNone, clientFs[1].AllowDismount(removalDrvNo));
		test(trsClientComplete == KRequestPending);
		test.Next(_L("Before all clients have completed, cancel the dismount"));
		fs.NotifyDismountCancel(trsClientComplete);
		test(trsClientComplete == KErrCancel);
		
		for(i=2; i< KNumClients; i++)
			{
			LOG_AND_TEST(KErrNone, clientFs[i].AllowDismount(removalDrvNo));
			test(trsClientComplete == KErrCancel);
			}

		// Check dismounting, responding, cancelling and forced remounting

		test.Next(_L("Register for notification of pending media removal (again) and check status"));
		for(i=0; i< KNumClients; i++)
			{
   			clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Notify clients of pending media removal and check status"));
   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
		test(trsClientComplete == KRequestPending);

		test.Next(_L("Check that client has notification of pending media removal"));
		for(i=0; i< KNumClients; i++)
			{
			test(trsClientNotify[i] == KErrNone);
			}

		test.Next(_L("Notify clients of pending media removal with another session and check status"));
   		fs.NotifyDismount(removalDrvNo, trsClientComplete1, EFsDismountNotifyClients);
		test(trsClientComplete1 == KErrInUse);

		TInt expectedAllowDismountRet = KErrNone;
		TInt expectedCompletionCode = KRequestPending;
		if(pass & 0x01)
			{
			test.Next(_L("No response from clients - Force a dismount"));
			test.Next(_L("...cancelling original request"));
	   		fs.NotifyDismountCancel(trsClientComplete);
			test(trsClientComplete == KErrCancel);
			test.Next(_L("...issuing a forced dismount request"));
	   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountForceDismount);

            User::WaitForRequest(trsClientComplete);
			test_KErrNone(trsClientComplete.Int());
			expectedAllowDismountRet = KErrNotReady;
			expectedCompletionCode = KErrNone;
			}

		test.Next(_L("Allow dismount and check response"));
		for(i=0; i < KNumClients; i++)
			{
			LOG_AND_TEST(expectedAllowDismountRet, clientFs[i].AllowDismount(removalDrvNo));
			if(i == KNumClients-1)
				test(trsClientComplete == KErrNone);
			else
				test(trsClientComplete == expectedCompletionCode);
			}

		// The last test should have dismounted the file system

		LOG_AND_TEST(KErrNotReady, file.Read(0,buf1));
		LOG_AND_TEST(KErrNotReady, dir.Read(dirEntry));

		test.Next(_L("FAT File System should now be dismounted from the drive"));
		LOG_AND_TEST(KErrNotReady, fs.DismountFileSystem(fsName, removalDrvNo));

		test.Next(_L("Mount MS FS on to the removable drive"));
		LOG_AND_TEST(KErrNone, fs.MountFileSystem(KMsFsyName, removalDrvNo));

		LOG_AND_TEST(KErrDisMounted, file.Read(0,buf1));
		LOG_AND_TEST(KErrDisMounted, dir.Read(dirEntry));

		test.Next(_L("Dismount MSFS normally"));
		LOG_AND_TEST(KErrNone, fs.DismountFileSystem(KMsFsyName, removalDrvNo));

		test.Next(_L("Mount FAT FS on to the removal drive"));
		LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

		LOG_AND_TEST(KErrNone, file.Read(0,buf1));
		LOG_AND_TEST(KErrEof, dir.Read(dirEntry));	// drive freshly formatted, so only root dir exists

		// Test multiple notifiers on a single session

		test.Next(_L("Register several notifiers for a single session and check status"));
		for(i=0; i< KNumClients; i++)
			{
   			clientFs[0].NotifyDismount(removalDrvNo, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Notify clients and verify all requests signalled"));
   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);

		test.Next(_L("Allow dismount 3 times from same session"));
		for(i=0; i< KNumClients; i++)
			{
			test(trsClientComplete == KRequestPending);
			test(trsClientNotify[i] == KErrNone);
			LOG_AND_TEST(KErrNone, clientFs[0].AllowDismount(removalDrvNo));
			}

		test.Next(_L("Verify that file system has been dismounted"));
		test(trsClientComplete == KErrNone);

		test.Next(_L("Mount FAT FS on to the removal drive"));
		LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

		// Test multiple notifiers on different drives

		const TInt KNumDrives = 1;

		test.Next(_L("Register several notifiers for different drives and check status"));
		for(i=0; i < KNumDrives; i++)
			{
   			clientFs[0].NotifyDismount(removalDrvNo + i, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Notify clients and verify all requests signalled"));
   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
		test(trsClientComplete == KRequestPending);

		test(trsClientNotify[0] == KErrNone);
		LOG_AND_TEST(KErrNone, clientFs[0].AllowDismount(removalDrvNo));
		for(i=1; i< KNumDrives; i++)
			{
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Verify that file system has been dismounted"));
		test(trsClientComplete == KErrNone);

		test.Next(_L("Check that file can be closed when filesystem is dismounted"));
		file.Close();
		dir.Close();

		test.Next(_L("Mount FAT FS on to the removal drive"));
		LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

		test.Next(_L("Cancel all outstanding notifiers for this session"));
   		clientFs[0].NotifyDismountCancel();
		for(i=1; i< KNumDrives; i++)
			{
			test(trsClientNotify[i] == KErrCancel);
			}

		// Test session disconnect
		test.Next(_L("Register for notification of pending media removal (again) and check status"));
		for(i=0; i< KNumClients; i++)
			{
   			clientFs[i].NotifyDismount(removalDrvNo, trsClientNotify[i]);
			test(trsClientNotify[i] == KRequestPending);
			}

		test.Next(_L("Close client sessions with outstanding notifiers"));
		for(i=0; i< KNumClients; i++)
			clientFs[i].Close();

		// Since all clients have been closed, the next stage should result in a dismount
		test.Next(_L("Notify clients of pending media removal and check status"));
		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
        User::WaitForRequest(trsClientComplete);
		test_KErrNone(trsClientComplete.Int());

		test.Next(_L("Mount FAT FS on to the removal drive"));
		LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

		TRequestStatus trs1;
		dismountFs1.NotifyDismount(removalDrvNo, trs1);
		test(trs1 == KRequestPending);

   		fs.NotifyDismount(removalDrvNo, trsClientComplete, EFsDismountNotifyClients);
		test(trsClientComplete == KRequestPending);

		fs.NotifyDismountCancel(trsClientComplete);

		fs.Close();
		dismountFs1.Close();
	}

	// Check that files/directories can't be opened on Mass Storage
	
	LOG_AND_TEST(KErrNone, fs.Connect());

	test.Next(_L("Dismount FAT File System"));
	LOG_AND_TEST(KErrNone, fs.DismountFileSystem(fsName, removalDrvNo));

	test.Next(_L("Mount MS FS on to the removable drive"));
	LOG_AND_TEST(KErrNone, fs.MountFileSystem(KMsFsyName, removalDrvNo));

	test.Next(_L("Attempting to open a file\n\r"));
	LOG_AND_TEST(KErrNotReady, file.Open(fs, fileName, EFileRead));

	test.Next(_L("Attempting to open a directory\n\r"));
    LOG_AND_TEST(KErrNotReady, dir.Open(fs, fileName, KEntryAttNormal));

	// Test fix for DEF058681 - Mass Storage reports VolumeName incorrectly.
	// Before the fix, CMassStorageMountCB::MountL() did not set the volume
	// name, resulting in a panic when attempting to copy a null descriptor.
	// Note that the volume name is still not returned client-side, since
	// CMassStorageMountCB::VolumeL() returns KErrNotReady
	TVolumeInfo volInfo;
	ret = fs.Volume(volInfo, removalDrvNo);
	LOG_AND_TEST(ret, KErrNotReady);
	LOG_AND_TEST(volInfo.iName.Length(), 0);

	// -------------------------------------------------
   
    // Get the boot sector info using MS FS and save it for later comparison
    TBuf8<CBW_LENGTH> cbwBuf; 
    TInt dCBWTag = 1234567;    // arbitrary, any number would do for this test.
 
    RDevTestUsbcClient usbcClient;

    // Open a session to LDD
    test.Next(_L("Open LDD"));
    LOG_AND_TEST(KErrNone, usbcClient.Open(0));

	// Build SCSI command test unit ready
    TBuf8<SCSI_UNIT_READY_LEN> unitReadyBuf;
    // Zero out the buf
    unitReadyBuf.FillZ(SCSI_UNIT_READY_LEN);

    createCBW(cbwBuf, dCBWTag, 0, 0, SCSI_UNIT_READY_LEN, unitReadyBuf);

    // Send test unit ready command
    test.Next(_L("Sending CBW with 'Unit Ready' cmd"));
	TRequestStatus status;
    usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH); 
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Read CSW
	test.Next(_L("Reading CSW"));
    TBuf8<CSW_LENGTH> cswBuf;
    usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Check dCSWTag
    TInt recvedCBWTag = extractInt(&cswBuf[4]);
    LOG_AND_TEST(dCBWTag,  recvedCBWTag);
     
    // Check bCSWStatus
    TInt bCSWStatus = cswBuf[CSW_LENGTH - 1];
    test.Printf(_L("CSW status: %d\n"), bCSWStatus);
    LOG_AND_TEST(bCSWStatus,  1);

	//============================================

	// Create a CBW for SCSI read (10) command to read the boot sector via MS FS 
    TBuf8<SCSI_READ_LEN> readBuf;  
    // 0: starting sector; 1: total blocks    
    createReadCmd(readBuf, 0, 1); 
    
    // 0x80: data-in to the host; 10: read (10) command length
    createCBW(cbwBuf, ++dCBWTag, BOOTSECTOR_SIZE, 0x80, 10, readBuf);
    

	// Send CBW to the LDD
    test.Next(_L("Send CBW with 'Read' Command"));
    usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone, status.Int());


	
	test.Next(_L("Reading bootsector data"));
    // Read the boot sector 
    TBuf8<BOOTSECTOR_SIZE> msBootSector;
    usbcClient.HostRead(status, EEndpoint2, msBootSector, BOOTSECTOR_SIZE); 
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone, status.Int());

    // Read CSW
    test.Next(_L("Reading CSW"));
    usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone, status.Int());

    // Check dCBWTag
    recvedCBWTag = extractInt(&cswBuf[4]);
    LOG_AND_TEST(dCBWTag,  recvedCBWTag);
     
    // Check bCSWStatus
    bCSWStatus = cswBuf[CSW_LENGTH - 1];
    test.Printf(_L("CSW status: %d\n"), bCSWStatus);
    
    LOG_AND_TEST(bCSWStatus,  0);

    // Compare FAT FS boot sector with MS FS boot sector
    // When accessing the medium through USB, it is accessed raw. That means 
    // we have to find the boot record (BPB), which may be in sector 0 if the 
    // medium has no MBR or elsewhere if it is has a MBR. (Details of the 
    // identification can be found in the FAT32 specification)
    TUint16 signature;
    
    signature = (TUint16) (msBootSector[KMBRSignatureOffset] | 
                msBootSector[KMBRSignatureOffset + 1] << 8);
                
	LOG_AND_TEST(signature, KBootRecordSignature);
	
	if(((msBootSector[0] == 0xEB && msBootSector[2] == 0x90) || (msBootSector[0] == 0xE9)) && 
		(msBootSector[16] >= 1) && ((msBootSector[21] == 0xF0) || (msBootSector[21] >= 0xF8)))
		{
		test.Printf(_L("BPB identified in sector 0.\r\n"));			
		}
	else
		{	
		test.Printf(_L("Assume sector 0 to be MBR - attempting to locate BPB\r\n"));
		// Read the offset to the first partition to find the boot record...
		// 32bit int stored as little endian
		TUint32 bootSectorLocation;
		bootSectorLocation = msBootSector[KMBRFirstPartitionSectorOffset]           | 
							 msBootSector[KMBRFirstPartitionSectorOffset + 1] << 8  |
							 msBootSector[KMBRFirstPartitionSectorOffset + 2] << 16 |
							 msBootSector[KMBRFirstPartitionSectorOffset + 3] << 24;
		
		test.Printf(_L("Reading Boot Sector from offset %d\r\n"), bootSectorLocation);

		// Create a CBW for SCSI read (10) command to read the boot sector via MS FS 
		TBuf8<SCSI_READ_LEN> readBuf;  
		// 0: starting sector; 1: total blocks    
		createReadCmd(readBuf, bootSectorLocation, 1); 
		
		// 0x80: data-in to the host; 10: read (10) command length
		createCBW(cbwBuf, ++dCBWTag, BOOTSECTOR_SIZE, 0x80, 10, readBuf);

		// Send CBW to the LDD
		test.Next(_L("Send CBW with 'Read' Command"));
		usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH);
		User::WaitForRequest(status);
		LOG_AND_TEST(KErrNone, status.Int());
		
		// Read the boot sector 
		usbcClient.HostRead(status, EEndpoint2, msBootSector, BOOTSECTOR_SIZE); 
		User::WaitForRequest(status);
		LOG_AND_TEST(KErrNone, status.Int());
		
		// Read CSW
		test.Next(_L("Reading CSW"));
		usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
		User::WaitForRequest(status);
		LOG_AND_TEST(KErrNone, status.Int());
		
		// Check dCBWTag
		recvedCBWTag = extractInt(&cswBuf[4]);
		LOG_AND_TEST(dCBWTag,  recvedCBWTag);
		 
		// Check bCSWStatus
		bCSWStatus = cswBuf[CSW_LENGTH - 1];
		test.Printf(_L("CSW status: %d\n"), bCSWStatus);
		
		LOG_AND_TEST(bCSWStatus,  0);
		}
    
    test (0 == fatBootSector.Compare(msBootSector));
    
    //
    // Create a CBW to prevent medium removal
    // 
    TBuf8<SCSI_MED_RMVL_LEN> tBuf; 
    // Zero out the buf
    tBuf.FillZ(SCSI_MED_RMVL_LEN);
    tBuf[0] = 0x1E;
    tBuf[4] = 1;        // prevent medium removal 

    cbwBuf.FillZ(CBW_LENGTH);
    createCBW(cbwBuf, ++dCBWTag, 0, 0, SCSI_MED_RMVL_LEN, tBuf);

    // Send prevent medium removal command
    test.Next(_L("Sending CBW with 'Prevent media removal' cmd"));
    usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH); 
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Read CSW
    test.Next(_L("Reading CSW"));
    usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone, status.Int());

    // Check dCSWTag
    recvedCBWTag = extractInt(&cswBuf[4]);
    LOG_AND_TEST(dCBWTag,  recvedCBWTag);
     
    // Check bCSWStatus
    bCSWStatus = cswBuf[CSW_LENGTH - 1];
    test.Printf(_L("CSW status: %d\n"), bCSWStatus);
    
	if(bCSWStatus ==  0)
		{
    
		// Try to unmount MS FS. This should fail as medium removal is disallowed
		test.Next(_L("Dismounting MSFS"));
		test(KErrNone != fs.DismountFileSystem(KMsFsyName, removalDrvNo));
		
		// 
		// Create a CBW to allow medium removal
		//
		// Zero out the buf
		tBuf.FillZ(SCSI_MED_RMVL_LEN);
		tBuf[0] = 0x1E;
		tBuf[4] = 0;        // allow medium removal 

		createCBW(cbwBuf, ++dCBWTag, 0, 0, SCSI_MED_RMVL_LEN, tBuf);

		// Send allow medium removal command
		test.Next(_L("Sending CBW with 'Allow media removal' cmd"));
		usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH); 
		User::WaitForRequest(status);
		LOG_AND_TEST(KErrNone, status.Int());

		// Read CSW
		test.Next(_L("Reading CSW"));
		usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
		User::WaitForRequest(status);
		LOG_AND_TEST(KErrNone, status.Int());

		// Check dCSWTag
		recvedCBWTag = extractInt(&cswBuf[4]);
		LOG_AND_TEST(dCBWTag, recvedCBWTag);
     
		// Check bCSWStatus
		bCSWStatus = cswBuf[CSW_LENGTH - 1];
		test.Printf(_L("CSW status: %d\n"), bCSWStatus);
		LOG_AND_TEST(bCSWStatus,  0);

		}
    else
		test.Printf(_L("Prevent Media Removal command not supported, skipping appropriate tests"));


    // Try to unmount MS FS again. This time it should succeed
	LOG_AND_TEST(KErrNone, fs.DismountFileSystem(KMsFsyName, removalDrvNo));

    // Read the boot sector while MS FS is unmounted, this should fail
    test(KErrNone != fatDiskF.Open(fs, removalDrvNo));
    fatDiskF.Close();    
    
    // Mount FAT FS on to the removal drive
    LOG_AND_TEST(KErrNone, fs.MountFileSystem(fsName, removalDrvNo));

	// Additional step for DEF079149: File server crash when re-adding 
	// MSFS.FSY. Before the fix was applied this call to RFs::AddFileSystem 
	// would cause a crash.
	LOG_AND_TEST(KErrAlreadyExists,fs.AddFileSystem(KMsFs));

    // Read the boot sector after FAT MS is mounted on to the removal drive
    LOG_AND_TEST(KErrNone,  fatDiskF.Open(fs, removalDrvNo));
    LOG_AND_TEST(KErrNone,  fatDiskF.Read(0, fatBootSector));
    fatDiskF.Close();

    createCBW(cbwBuf, ++dCBWTag, 0, 0, SCSI_UNIT_READY_LEN, unitReadyBuf);

    // Send test unit ready command
    test.Next(_L("Sending CBW with 'Unit Ready' cmd"));
    usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH); 
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Read CSW
    usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Check dCSWTag
    recvedCBWTag = extractInt(&cswBuf[4]);
    LOG_AND_TEST(dCBWTag,  recvedCBWTag);
     
    // Check bCSWStatus
    bCSWStatus = cswBuf[CSW_LENGTH - 1];
    test.Printf(_L("CSW status: %d\n"), bCSWStatus);
    LOG_AND_TEST(bCSWStatus,  1);
 
    // 
    // Read MS FS using SCSI read command, this should fail as
    // FAT FS is mounted instead
    //
    
    // 0x80: data-in to the host; 10: read (10) command length

    createCBW(cbwBuf, ++dCBWTag, BOOTSECTOR_SIZE, 0x80, 10, readBuf);

    // Send CBW to the LDD
    test.Next(_L("SEnding CBW with 'Read'"));
    usbcClient.HostWrite(status, EEndpoint1, cbwBuf, CBW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Read the sector 
    usbcClient.HostRead(status, EEndpoint2, msBootSector, BOOTSECTOR_SIZE); 
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());


    // Read CSW
    usbcClient.HostRead(status, EEndpoint2, cswBuf, CSW_LENGTH);
    User::WaitForRequest(status);
    LOG_AND_TEST(KErrNone,  status.Int());

    // Check dCSWTag
    recvedCBWTag = extractInt(&cswBuf[4]);
    LOG_AND_TEST(dCBWTag , recvedCBWTag);
     
    // Check bCSWStatus
    bCSWStatus = cswBuf[CSW_LENGTH - 1];
    test.Printf(_L("CSW status: %d\n"), bCSWStatus);
    test(bCSWStatus != 0);
    
   
    // Read FAT FS using RFs API
    LOG_AND_TEST(KErrNone,  fatDiskF.Open(fs, removalDrvNo));
    LOG_AND_TEST(KErrNone,  fatDiskF.Read(0, fatBootSector));
    fatDiskF.Close();    
    
    // Stop usb mass storage device
    LOG_AND_TEST(KErrNone,  usbMs.Stop());
    usbMs.Close();
	User::After(1000000);
    
    ret = fs.RemoveFileSystem(KMsFsyName);
	fs.Close();
	
	usbcClient.Close();
	ret = User::FreeLogicalDevice(_L("USBC"));
	LOG_AND_TEST(ret,  KErrNone);

	test.Printf(_L("Exiting test\r\n"));	
	__UHEAP_MARKEND;
	}
	


GLDEF_C void CallTestsL()
//
// Do all tests
//
	{
    // Parse the CommandLine arguments: removal drive and non-removal drive
    ParseCommandArguments();

    // Run test
	test.Start( _L("Test mountstart") );
    doComponentTest();
    test.End();
    }
