// 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:
// f32\sfat\sl_drv.cpp
// 
//

#include "sl_std.h"
#include "sl_cache.h"

const TInt KMaxRecoverableRetries=10;
const TInt KMaxCriticalRetries=10;


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


TDriveInterface::TDriveInterface() 
                   :iMount(NULL)
{
}

/**
    Initialise the interface object.
    @param  aMount the CFatMountCB that owns this object
*/
TBool TDriveInterface::Init(CFatMountCB* aMount)
{
    ASSERT(aMount);
    iMount = aMount;
	aMount->LocalDrive()->SetMount(aMount);
    return iProxyDrive.Init(aMount->LocalDrive());
}

/**
    Close the interface to the media driver
*/
void TDriveInterface::Close()
{
	 if(iMount)
		{
		iMount->LocalDrive()->SetMount(NULL);
        }

     iMount = NULL;
}

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

/**
    Read data from the media via CProxyDrive interface.
    This is non-critical read: on error Non-critical notifier is involved

    @param  aPos        absolute media position
    @param  aLength     how many bytes to read
    @param  aTrg        data descriptor

    @return KErrNone - success
    @return KErrNotReady - non-critical error
    @return KErrCorrupt - an illegal write is detected
    @return KErrBadPower - failure due to low power

*/
TInt TDriveInterface::ReadNonCritical(TInt64 aPos, TInt aLength, TDes8& aTrg) const
{
    TInt nRes = KErrNone;
    TInt cntRetry = KMaxRecoverableRetries;

    //__PRINT2(_L("#=+++ Read_nc1: pos:%LU, len:%u"), aPos, aLength);

    for(;;)
    {
        nRes = iProxyDrive.Read(aPos,aLength,aTrg);
        if (nRes==KErrNone)
            break;

        __PRINT4(_L("TDriveInterface::ReadNonCritical() failure! drv:%d Posl=%LU len=%d retval=%d"), iMount->DriveNumber(), aPos, aLength, nRes);
        
        if(--cntRetry <= 0)
        {
            nRes = KErrCorrupt;
            break;
        }

        nRes = HandleRecoverableError(nRes);
        if (nRes !=ERetry)
            break;
    }

    return nRes;
}

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

/**
    Read data from the media via CProxyDrive interface. 
    This is non-critical read: on error Non-critical notifier is involved

    @param  aPos        absolute media position
    @param  aLength     how many bytes to read
    @param  aTrg        data descriptor
    @param  aMessage
    @param  anOffset

    @return KErrNone - success
    @return KErrNotReady - non-critical error
    @return KErrCorrupt - an illegal write is detected
    @return KErrBadPower - failure due to low power

*/
TInt TDriveInterface::ReadNonCritical(TInt64 aPos,TInt aLength,const TAny* aTrg,const RMessagePtr2 &aMessage,TInt anOffset) const
{
    //__PRINT2(_L("#=+++ Read_nc2: pos:%LU, len:%u"), aPos, aLength);

    TInt nRes = KErrNone;
    TInt cntRetry = KMaxRecoverableRetries;

    for(;;)
    {
        nRes = iProxyDrive.Read(aPos, aLength, aTrg, aMessage, anOffset);
        if (nRes==KErrNone)
            break;

        __PRINT4(_L("TDriveInterface::ReadNonCritical() Failure! drv:%d aPosl=%d len=%d anOffset=%d"), iMount->DriveNumber(), aPos,aLength, anOffset);
        
        if(--cntRetry <= 0)
        {
            nRes = KErrCorrupt;
            break;
        }

        nRes = HandleRecoverableError(nRes);
        if (nRes !=ERetry)
            break;
    }

    return nRes;
}

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

/**
    Read data from the media via CProxyDrive interface with a critical notifier.
    This method shall be used to read critical filesystem data, such as directory entries, FAT data.

    @param  aPos        absolute media position
    @param  aLength     how many bytes to read
    @param  aTrg        data descriptor

    @return KErrNone - success
    @return KErrNotReady - non-critical error
    @return KErrCorrupt - an illegal write is detected
    @return KErrAbort - user aborted read
*/
TInt TDriveInterface::ReadCritical(TInt64 aPos,TInt aLength,TDes8& aTrg) const
{
    //__PRINT2(_L("#=+++ Read_C: pos:%LU, len:%u"), aPos, aLength);

    TInt nRes = KErrNone;

    for(;;)
    {
        nRes = iProxyDrive.Read(aPos, aLength, aTrg);
		if(nRes == KErrNone)
            break;

		__PRINT4(_L("TDriveInterface::ReadCritical() Error! drv:%d Posl=%LU len=%d retval=%d"), iMount->DriveNumber(), aPos, aLength, nRes);
		
        nRes=HandleCriticalError(nRes);
		if (nRes != ERetry)
            break;
    }

    return nRes;
}

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

/**
    Write data to the media via CProxyDrive interface.
    
    @param  aPos        absolute media position
    @param  aLength     how many bytes to write
    @param  aSrc        pointer to the data 
    @param  aMessage
    @param  anOffset
    
    @return KErrNone - success
    @return KErrNotReady - non-critical error
    @return KErrBadPower - write not attempted due to low batteries
    @return KErrCorrupt - an illegal write is detected
    @return KErrAccessDenied - write to protected media
*/
TInt TDriveInterface::WriteNonCritical(TInt64 aPos, TInt aLength, const TAny* aSrc, const RMessagePtr2 &aMessage, TInt anOffset)
{
    //__PRINT2(_L("#=+++ Write_NC: pos:%LU, len:%u"), aPos, aLength);

    
    TInt nRes = KErrNone;
    TInt cntRetry = KMaxRecoverableRetries;

    for(;;)
    {
        iMount->OpenMountForWrite(); //-- make a callback to CFatMountCB to perform some actions on 1st write.
        nRes = iProxyDrive.Write(aPos, aLength, aSrc, aMessage, anOffset);
        if (nRes==KErrNone)
            break;

        __PRINT4(_L("TDriveInterface::WriteNonCritical() failure! drv:%d, Pos=%LU len=%d anOffset=%d"), iMount->DriveNumber(), aPos, aLength, anOffset);
        
        if(--cntRetry <= 0)
        {
            nRes = KErrCorrupt;
            break;
        }

        nRes = HandleRecoverableError(nRes);
        if (nRes !=ERetry)
            break;
    }


    return nRes;
}

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

/**
    Write data to the media via CProxyDrive interface. On error this method can invoke a critical notifier.
    This method is intended to be called for the filesstem critical data, i.e. FAT metadata, such as directory entries,
    FAT table etc.
    
    @param  aPos  absolute media position
    @param  aSrc  descriptor with the source data
    
    @return KErrNone - success
    @return KErrNotReady - non-critical error
    @return KErrBadPower - write not attempted due to low batteries
    @return KErrCorrupt - an illegal write is detected
    @return KErrAccessDenied - write to protected media
*/
TInt TDriveInterface::WriteCritical(TInt64 aPos, const TDesC8& aSrc)
{
    //__PRINT2(_L("#=+++ Write_C: pos:%LU, len:%u"), aPos, aSrc.Length());

    TInt nRes = KErrNone;

#ifdef _DEBUG
    
    TBool simulatedWriteFailure = EFalse; //-- if true it means that the write failure has been simulated

    //-- debug interface to simulate write failure
	if(iMount->IsWriteFail())
    {
		if(iMount->WriteFailCount() != 0)
        {
            iMount->DecWriteFailCount();
        }
        else
		{//-- simulate write failure
			if(iMount->WriteFailError()==-99)
				UserSvr::ResetMachine(EStartupWarmReset);
			else
			{
			    //-- invalidate caches, because actual write to the drive isn't going to happen
                if(iMount->RawDisk().DirCacheInterface())
                    iMount->RawDisk().DirCacheInterface()->InvalidateCache();

                iMount->SetWriteFail(EFalse);
				
                TRAP_IGNORE(iMount->RawDisk().InvalidateUidCache()); //-- invalidate whole UID data cache
                TRAP_IGNORE(iMount->FAT().InvalidateCacheL());       //-- invalidate whole FAT cache
                
                iMount->InvalidateLeafDirCache();

                nRes = iMount->WriteFailError(); 
                simulatedWriteFailure = ETrue; //-- won't perform actual write later
                __PRINT4(_L("TDriveInterface::WriteCritical() Simulating write failure. drv:%d, aPos=%LU len=%d Code=%d"), iMount->DriveNumber(), aPos,aSrc.Length(),nRes);

			}
		}
    }//if(iMount->IsWriteFail())

    if(!simulatedWriteFailure)
#endif // _DEBUG
    {
        //-- try to write data until success or user gives up
	    for(;;)
	    {
            for(TInt i=0; i<KMaxCriticalRetries; i++)
            {
                iMount->OpenMountForWrite();  //-- make a callback to CFatMountCB to perform some actions on 1st write.
                nRes=iProxyDrive.Write(aPos,aSrc);
			    if (nRes==KErrNone)
                    return nRes;
		    }

            //-- write error occured
            __PRINT4(_L("TDriveInterface::WriteCritical() failure! drv:%d, aPos=%LU len=%d retval=%d"), iMount->DriveNumber(), aPos,aSrc.Length(),nRes);

            nRes=HandleCriticalError(nRes);
            if (nRes!=ERetry)
                break;

	    }//for(;;)
    
    }// if(!simulatedWriteFailure)
    
    return nRes;
}


/**
    Get Last Error Info from the proxy drive
        
    @param  aErrorInfo data descriptor for the error info.
    @return KErrNone - success, interrogate aErrorInfo for further info
    @return KErrNotSupported - media driver does not support
*/
TInt TDriveInterface::GetLastErrorInfo(TDes8& aErrorInfo) const
{
    return iProxyDrive.GetLastErrorInfo(aErrorInfo);
}


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

/**
    Handle critical error

    @param aResult result from the media driver (error code)

    @return ERetry - Attempt operation again
    @return KErrAbort - User aborted notifier
    @return KErrAccessDenied - media is read only
    @return KErrCorrupt - cf-card is corrupt
*/
TInt TDriveInterface::HandleCriticalError(TInt aResult) const
	{
    __PRINT2(_L("TDriveInterface::HandleCriticalError drv:%d, code:%d"), iMount->DriveNumber(),aResult);

	TLocaleMessage line1;
	TLocaleMessage line2;

	TInt r=KErrAbort;

	if (aResult==KErrLocked)
	{
		r=KErrLocked;
		goto End;
	}

	if (aResult==KErrAccessDenied)
		{
		r=KErrAccessDenied;
		goto End;
		}
	
	if (aResult==KErrArgument || aResult==KErrBadDescriptor)
		{
		r=KErrCorrupt;
		goto End;
		}

	if (iMount->Drive().IsChanged())
		{//-- check if the media we accessing is the same as it used to be
          if(iMount->CheckVolumeTheSame())
        	{//-- the media is the same
			if(!IsDriveWriteProtected())
				{
				iMount->Drive().SetChanged(EFalse);
				r=ERetry;
				goto End;
				}
			}
		}

	if (aResult==KErrAbort && !iMount->Drive().IsChanged())
		{
		r=ERetry;
		goto End;
		}

	if (aResult==KErrBadPower)
		{
		line1=EFileServer_LowPowerLine1;
		line2=EFileServer_LowPowerLine2;
		}
	else if (iMount->Drive().IsChanged())
		{
		line1=EFileServer_PutTheCardBackLine1;
		line2=EFileServer_PutTheCardBackLine2;
		}
	else
		{
		line1=EFileServer_DiskErrorLine1;
		line2=EFileServer_DiskErrorLine2;
		}
	
	if (NotifyUser())
		{
		FOREVER
			{
			TInt buttonVal;
			TInt ret=iMount->Notifier()->Notify(TLocaleMessageText(line1),
												TLocaleMessageText(line2),
												TLocaleMessageText(EFileServer_Button1),
												TLocaleMessageText(EFileServer_Button2),
												buttonVal);
			if (ret!=KErrNone)
				break; 
			if (buttonVal!=1)
				break; // Abort

			if (iMount->Drive().IsChanged())
				{
//
// Without this code, retry will indiscriminately write over whatever disk happens to be present.
// However if the write error is to the bootsector remounting will always fail because the boot
// sector will have changed and hence the disk is useless.
// 
                if(!iMount->CheckVolumeTheSame())
                    continue; //-- the media isn't the same as originally mounted; continue asking

                if(IsDriveWriteProtected())
                    continue; //-- still can not write to the drive


				iMount->Drive().SetChanged(EFalse);
				}

			r=ERetry; // Retry
			break;
			}
		}
End:
	return(r);
	}

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

/**
    Handle recoverable error

    @param aResult result from the media driver (error code)

    @return ERetry - retry write
    @return KErrCorrupt - media is corrupt
    @return KErrBadPower - low power failure
    @return KErrNotReady - non-critical error
*/
TInt TDriveInterface::HandleRecoverableError(TInt aResult) const
	{
	__PRINT2(_L("TDriveInterface::HandleRecoverableError drv:%d, code:%d"), iMount->DriveNumber(),aResult);

	if (aResult==KErrAccessDenied)
		return(KErrAccessDenied);
	if (aResult == KErrLocked)
		return KErrLocked;
	if (aResult==KErrArgument || aResult==KErrBadDescriptor)
		return(KErrCorrupt);
	if (aResult==KErrBadPower)
		return(KErrBadPower);
	if (aResult==KErrDied)	// client thread died
		return(KErrDied);
	if (iMount->Drive().IsChanged())
		{

        if(! iMount->CheckVolumeTheSame())
            {//-- the media is different now.
            return KErrNotReady;
			}
		else if(!IsRecoverableRemount())
			{
			return KErrAccessDenied;
			}
		}
	return(ERetry);
	}	

/** @return true if the mount can be remounted for a recoverable error */
TBool TDriveInterface::IsRecoverableRemount() const
	{
	if(IsDriveWriteProtected()&&(iMount->Drive().IsWriteableResource()||iMount->Drive().IsCurrentWriteFunction()))
		return(EFalse);
	return(ETrue);
	}

/** return true if the media is write protected */
TBool TDriveInterface::IsDriveWriteProtected() const
	{
	TLocalDriveCapsV2Buf localDriveCaps;
    TInt r=iProxyDrive.Caps(localDriveCaps);

	if(r!=KErrNone)
		return(EFalse);

	return((localDriveCaps().iMediaAtt&KMediaAttWriteProtected)!=0);
	}



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

TDriveInterface::XProxyDriveWrapper::XProxyDriveWrapper() 
                   :iLocalDrive(0) 
{
    TInt nRes = iLock.CreateLocal();
    ASSERT(nRes == KErrNone);
    (void)nRes; 
}


TDriveInterface::XProxyDriveWrapper::~XProxyDriveWrapper() 
{
    iLock.Close();
}

/** 
    Initialise interface wrapper.
    @param  aProxyDrive pointer to the raw drive access interface
    @return true on success
*/
TBool TDriveInterface::XProxyDriveWrapper::Init(CProxyDrive* aProxyDrive) 
{
    ASSERT(aProxyDrive);
    if(!iLock.Handle()) //-- the mutex must have been created by constructor
        return EFalse;
    
    iLocalDrive = aProxyDrive;
    return ETrue;
}

//-- see original TDriveInterface methods

TInt TDriveInterface::XProxyDriveWrapper::Read(TInt64 aPos,TInt aLength,const TAny* aTrg,const RMessagePtr2 &aMessage,TInt anOffset) const
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->Read(aPos, aLength, aTrg, aMessage.Handle(), anOffset);
    LeaveCriticalSection();
    return nRes;
}
       
TInt TDriveInterface::XProxyDriveWrapper::Read(TInt64 aPos,TInt aLength,TDes8& aTrg) const
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->Read(aPos, aLength, aTrg);
    LeaveCriticalSection();
    return nRes;
}

TInt TDriveInterface::XProxyDriveWrapper::Write(TInt64 aPos,TInt aLength,const TAny* aSrc,const RMessagePtr2 &aMessage,TInt anOffset)
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->Write(aPos, aLength, aSrc, aMessage.Handle(), anOffset);
    LeaveCriticalSection();
    return nRes;
}

TInt TDriveInterface::XProxyDriveWrapper::Write(TInt64 aPos, const TDesC8& aSrc)
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->Write(aPos, aSrc);
    LeaveCriticalSection();
    return nRes;
}

TInt TDriveInterface::XProxyDriveWrapper::GetLastErrorInfo(TDes8& aErrorInfo) const
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->GetLastErrorInfo(aErrorInfo);
    LeaveCriticalSection();
    return nRes;
}

TInt TDriveInterface::XProxyDriveWrapper::Caps(TDes8& anInfo) const
{
    EnterCriticalSection();
    TInt nRes = iLocalDrive->Caps(anInfo);
    LeaveCriticalSection();
    return nRes;
}












