/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "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:  Definitions for the class CustomUser.
*
*/

#include <f32file.h>
#include <utf.h>
#include "customuser.h"
#include "analyzetoolmainallocator.h"
#include "analyzetoolallocator.h"
#include "atlog.h"
#include "analyzetoolmemoryallocator.h"
#include "analyzetoolpanics.pan"
#include "atstorageservercommon.h"
#include "atdriveinfo.h"
#include <analyzetool/analyzetooltraceconstants.h>

#ifdef USE_CLEANER_DLL
// Global variable to detect dll attach & detach in process.
// Note! This is initialized after SetupThreadHeap so its not usable there.
// This is used to store the main thread id and track when the process ends
// to load the cleaner dll with call back feature to cleanup allocator at the
// last possible phase.
#include <analyzetool/analyzetoolcleaner.h>

// CONSTANTS
const TInt KAToolCleanerOrdinal = 1;

class TAnalyzeToolGlobalTracker : public TAnalyzeToolCleanerBase
    {
public:
    /* Main thread id */
    TThreadId iMainId;
    
    /* Inform if panic occured */
    TBool iPanic;
    
    // -----------------------------------------------------------------------------
    // TAnalyzeToolGlobalTracker::TAnalyzeToolGlobalTracker()
    // C++ default constructor 
    // -----------------------------------------------------------------------------
    //
    TAnalyzeToolGlobalTracker()
        {
        LOGSTR1( "ATMH TAnalyzeToolGlobalTracker::TAnalyzeToolGlobalTracker()" );
        
        iPanic = EFalse; // no panic occured
        iMainId = RThread().Id(); // set main thread id
        LOGSTR2( "ATMH TAnalyzeToolGlobalTracker() > Main id set: %d", 
                iMainId.operator TUint() );
        }
    
    // -----------------------------------------------------------------------------
    // TAnalyzeToolGlobalTracker::~TAnalyzeToolGlobalTracker()
    // Destructor.
    // -----------------------------------------------------------------------------
    //
    ~TAnalyzeToolGlobalTracker()
        {
        LOGSTR1( "ATMH TAnalyzeToolGlobalTracker::~TAnalyzeToolGlobalTracker()" );
        
        // We dont load dll if panic has happened (uninstallation has been done).
        if ( iPanic )
            {
            LOGSTR1( "ATMH ~TAnalyzeToolGlobalTracker > Panic set not loading cleaner dll." );
            return;
            }
        
        LOGSTR1( "ATMH ~TAnalyzeToolGlobalTracker > about to load cleaner dll" );
        // Load cleaner library and set a call back to our cleanup
        RLibrary lib;
        TInt error( lib.Load( KATCleanerDllName ) );
        if ( error == KErrNone )
            {
            // Set address to point to ourself
            TLibraryFunction func = lib.Lookup( KAToolCleanerOrdinal ); // Ordinal 1 of the dll
            ATCLEANERTABLE* cleaner = (ATCLEANERTABLE*) func(); // Use function to get address
            cleaner->At( 0 ) = (TUint32) this; // Set address
            LOGSTR1( "ATMH ~TAnalyzeToolGlobalTracker() > cleaner dll loaded and call back set" );
            }
        else
            {
            // Error loading cleanup dll
            LOGSTR2( "ATMH ~TAnalyzeToolGlobalTracker() > cleaner dll load error(%i) uninstalling allocator now!", 
                    error );
            Cleanup();
            }
        }
    
    // -----------------------------------------------------------------------------
    // TAnalyzeToolGlobalTracker::Cleanup()
    // 
    // -----------------------------------------------------------------------------
    //
    void Cleanup()
        {
        LOGSTR1( "ATMH TAnalyzeToolGlobalTracker::Cleanup() - allocator uninstall" );
        
        // Uninstall allocator
        ( (RAnalyzeToolMemoryAllocator&) User::Allocator() ).Uninstall();
        }
    
    };

// Global variable definition.
TAnalyzeToolGlobalTracker gGlobalTracker;
#endif

// CONSTANTS
// When needed, update the version number directly inside _LIT macro.
// Constant for the atool API(staticlib) version.
_LIT( KAtoolApiVersion, "1.7.5" );

// Version number buffer length
const TInt KAtoolVersionNumberLength = 10;

// Wrong version error code
const TInt KAtoolVersionError = -1999;

// Version number separator
_LIT( KVersionSeparator, ";" );

// Incorrect version error strings 
_LIT( KIncorrectText, "ERROR_OCCURED INCORRECT_ATOOL_VERSION [API v.%S][ATOOL v.%S]" );
_LIT( KIncorrectTextTrace, "PCSS " );

// -----------------------------------------------------------------------------
// CustomUser::Panic()
// Overloaded User::Panic() function
// -----------------------------------------------------------------------------
//
EXPORT_C void CustomUser::Panic( const TDesC& aCategory, TInt aReason )
    {
    LOGSTR3( "ATMH CustomUser::Panic() %S %i", &aCategory, aReason );
    
#ifdef USE_CLEANER_DLL
    // Set global tracker that panic has happened.
    gGlobalTracker.iPanic = ETrue;
#endif
    
    // Uninstall thread's RAllocator
    ( (RAnalyzeToolMemoryAllocator&) User::Allocator() ).Uninstall();
       
    // Call the "real" User::Panic()
    User::Panic( aCategory, aReason );
    }

// -----------------------------------------------------------------------------
// CustomUser::Exit()
// Overloaded User::Exit() function
// -----------------------------------------------------------------------------
//
EXPORT_C void CustomUser::Exit( TInt aReason )
    {
    LOGSTR3( "ATMH CustomUser::Exit() %i %i", aReason, RThread().Id().Id() );
    
    if ( aReason != KAtoolVersionError )
    	{
#ifdef USE_CLEANER_DLL
        // Only uninstall allocator if its not the process main/first thread.
        LOGSTR3( "ATMH CustomUser::Exit() - Thread id: %d - Main Id: %d",
                RThread().Id().operator TUint(), gGlobalTracker.iMainId.operator TUint() );
        
        if ( RThread().Id() != gGlobalTracker.iMainId )
            {
            LOGSTR2("ATMH CustomUser::Exit() - Calling allocator uninstall in thread: %d" , RThread().Id().operator TUint() );
            ( (RAnalyzeToolMemoryAllocator&) User::Allocator() ).Uninstall();
            }
#else
    	// Uninstall thread's RAllocator
    	( (RAnalyzeToolMemoryAllocator&) User::Allocator() ).Uninstall();
    	LOGSTR1( "ATMH CustomUser::Exit() - about to User::Exit" );
#endif
    	}
    
    // Call the "real" User::Exit()
    User::Exit( aReason );
    }

// -----------------------------------------------------------------------------
// CustomUser::SetCritical()
// Overloaded User::SetCritical() function which returns
// KErrNone, if successful; KErrArgument, if EAllThreadsCritical is 
// passed - this is a state associated with a process, and you use 
// User::SetProcessCritical() to set it.
// -----------------------------------------------------------------------------
//
EXPORT_C TInt CustomUser::SetCritical( User::TCritical aCritical )
    {
    LOGSTR1( "ATMH CustomUser::SetCritical()" );
    // Check the given User::TCritical type
    if ( aCritical == User::EAllThreadsCritical )
        {
        return KErrArgument;
        }
    else
        {
        return KErrNone;
        }
    }
  
// -----------------------------------------------------------------------------
// CustomUser::SetProcessCritical()
// Overloaded User::SetProcessCritical() function
// KErrNone, if successful; KErrArgument, if either EProcessCritical or 
// EProcessPermanent is passed - these are states associated with a 
// thread, and you use User::SetCritical() to set them.
// -----------------------------------------------------------------------------
//
EXPORT_C TInt CustomUser::SetProcessCritical( User::TCritical aCritical )
    {
    LOGSTR1( "ATMH CustomUser::SetProcessCritical()" );
     // Check the given User::TCritical type 
    if ( aCritical == User::EProcessCritical || 
         User::EProcessPermanent == aCritical )
        {
        return KErrArgument;
        }
    else
        {
        return KErrNone;
        }
    }

// -----------------------------------------------------------------------------
// CustomUser::SetupThreadHeap()
// Overloaded UserHeap::SetupThreadHeap function
// -----------------------------------------------------------------------------
//
EXPORT_C TInt CustomUser::SetupThreadHeap( TBool aNotFirst, 
    SStdEpocThreadCreateInfo& aInfo, const TFileName& aFileName,
    TUint32 aLogOption, TUint32 aIsDebug, const TATVersion& aVersion,
    TUint32 aAllocCallStackSize, TUint32 aFreeCallStackSize,
    TRefByValue<const TDesC> aFmt, ... )
    {
    LOGSTR1( "ATMH CustomUser::SetupThreadHeap()" );
    LOGSTR2( "ATMH > Thread id(%d)", RThread().Id().operator TUint() );
    
    // Add handling of the argument list here.
        
    TInt ret( KErrNone );    
    // Check version number
    TBuf<KAtoolVersionNumberLength> atoolVer;
    if ( CheckVersion( aVersion, atoolVer ) != KErrNone )
    	{
    	LOGSTR1( "ATMH > Wrong API version > Inform user and Exit." );
    	ReportIncorrectVersion( aLogOption, aFileName, atoolVer );
    	return KAtoolVersionError;
    	}
    
    // Check is this shared heap
    if ( aInfo.iAllocator == NULL )
        {
        LOGSTR1( "ATMH creating a new heap" );
        // RAllocator is NULL so heap is not shared, creating a new heap
        ret = UserHeap::SetupThreadHeap( aNotFirst, aInfo );
        __ASSERT_ALWAYS( KErrNone == ret, AssertPanic( EFailedToCreateHeap ) );
        
#if ( SYMBIAN_VERSION_SUPPORT >= SYMBIAN_3 )
    #ifndef __WINS__
        // Set dummy Tls value
        TAny* dummyPtr( NULL );
        TInt setErr( UserSvr::DllSetTls( KDummyHandle, dummyPtr ) );
        LOGSTR2( "ATMH > Set Tls err(%i)", setErr );
    #endif
#endif
        // Install the RAllocator
        aInfo.iAllocator = &InstallAllocator( aNotFirst, aFileName, aLogOption, aIsDebug,
                aAllocCallStackSize, aFreeCallStackSize );
        }
    else
        {
        LOGSTR1( "ATMH sharing the heap" );
        // The heap is shared. Acquire pointer to the original heap
        RAnalyzeToolMemoryAllocator* allocator = 
			(RAnalyzeToolMemoryAllocator*) aInfo.iAllocator;
        // Share the heap
        allocator->ShareHeap();
        // Switch thread heap 
        User::SwitchAllocator( allocator );
        }
    return ret;
    }

// -----------------------------------------------------------------------------
// CustomUser::InstallAllocator
// Installs the RAllocator
// -----------------------------------------------------------------------------
//
//lint -e{429} suppress "Custodial pointer 'allocator' has not been freed or returned"
EXPORT_C RAllocator& CustomUser::InstallAllocator( TBool aNotFirst, 
	const TFileName& aFileName, TUint32 aLogOption, TUint32 aIsDebug,
	TUint32 aAllocCallStackSize, TUint32 aFreeCallStackSize )
    {
    LOGSTR1( "ATMH CustomUser::InstallAllocator()" );
    
    // Open handle to the device driver
    RAnalyzeTool analyzetool;
    TInt error = analyzetool.Open();
    
    // Check if the device driver has already loaded
    if ( KErrNone == error )
        {
        LOGSTR1( "ATMH CustomUser::InstallAllocator() - analyzetool.Open() returned KErrNone" );
        // The device driver has already loaded
        // Get pointer to the main thread allocator
        TMainThreadParamsBuf params;
        params().iProcessId = RProcess().Id().operator TUint();
        error = analyzetool.MainThreadAlloctor( params );

        __ASSERT_ALWAYS( KErrNone == error, AssertPanic( ECantOpenHandle ) );
        
        // Close handle to the device driver
        analyzetool.Close();
        
        // Is this the first thread of the program
        if ( params().iAlone )
            {
            LOGSTR1( "ATMH CustomUser::InstallAllocator() - first thread of the program" );
            // Only one thread in the program. Must be main thread
            RAnalyzeToolMainAllocator* allocator = 
				new RAnalyzeToolMainAllocator( aNotFirst, aFileName, aLogOption,
				                               aIsDebug, aAllocCallStackSize, aFreeCallStackSize );
            
            __ASSERT_ALWAYS( allocator != NULL, AssertPanic( ENoMemory ) );
            
            // Change threads allocator
            User::SwitchAllocator( allocator );
            
            // Return reference to the RAllocator
            return *allocator;
            }
        // This is not the first thread. A new thread with a new heap created
        else
            {
            LOGSTR1( "ATMH CustomUser::InstallAllocator() - create a new allocator for the new thread" );
            // Create new RAllocator with handles from the main thread
            RAnalyzeToolAllocator* allocator = new RAnalyzeToolAllocator( 
                    aNotFirst,
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->StorageServer(), 
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->Codeblocks(), 
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->Mutex(), 
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->ProcessId(), 
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->AnalyzeTool(),
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->StorageServerOpen(),
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->LogOption(),
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->AllocMaxCallStack(),
                    ((RAnalyzeToolMainAllocator*)params().iAllocator)->FreeMaxCallStack() );

            __ASSERT_ALWAYS( allocator != NULL, AssertPanic( ENoMemory ) );
            
            // Change threads allocator
            User::SwitchAllocator( allocator );
            
            // Return reference to the RAllocator
            return *allocator;
            }
        }
    // The device driver does not exists so this must be the first thread
    else
        {
        LOGSTR1( "ATMH CustomUser::InstallAllocator() - analyzetool.Open() returned error, creating DD" );
        RAnalyzeToolMainAllocator* allocator = 
			new RAnalyzeToolMainAllocator( aNotFirst, aFileName, aLogOption, aIsDebug,
			        aAllocCallStackSize, aFreeCallStackSize );
        
        __ASSERT_ALWAYS( allocator != NULL, AssertPanic( ENoMemory ) );
        
        // Change threads allocator
        User::SwitchAllocator( allocator );

        // Return reference to the RAllocator
        return *allocator;
        }
    } 
    
// -----------------------------------------------------------------------------
// CustomUser::CheckVersion
// Check atool version
// -----------------------------------------------------------------------------
//
TInt CustomUser::CheckVersion( const TATVersion& aVersion, TDes& aToolVersion )
    { 
    LOGSTR2( "ATMH CustomUser::CheckVersion(), aVersion( %S )", &aVersion );
    
    TFileName version;
    version.Copy( aVersion );
    TBuf<KAtoolVersionNumberLength> apiVer;
    	
    // Find separator place
    TInt findplace( version.Find( KVersionSeparator() ) );
    // Parse API version first [x.x.x;x.x.x]
    if ( findplace >= 0 && findplace <= apiVer.MaxLength() )
		{
		apiVer.Copy( version.Mid( 0, findplace ) ); 
		version.Delete( 0, findplace + KVersionSeparator().Length() );
		}
 
    if ( version.Length() <= aToolVersion.MaxLength() )
    	{
    	aToolVersion.Copy( version );
    	if ( aToolVersion.Compare( KAtoolApiVersion ) == KErrNone &&
    		 apiVer.Length() == 0 )
    		{
    		// Support 1.5.0 version (Version info: [1.5.0])
    		apiVer.Copy( version );
    		}
    	}
    
    LOGSTR3( "ATMH > API version( %S ), ATOOL version( %S )", 
    		&apiVer, &aToolVersion );
        
    // Check version numbers 
    if ( apiVer.Compare( KAtoolApiVersion ) == KErrNone )
    	{
    	return KErrNone;
    	}
    return KErrCancel;    
    }

// -----------------------------------------------------------------------------
// CustomUser::ReportIncorrectVersion
// Function for showing incorrect version information
// -----------------------------------------------------------------------------
//
void CustomUser::ReportIncorrectVersion( const TUint32 aLogOption,
	const TFileName& aFileName, const TDes& aToolVersion )
	{
	LOGSTR2( "ATMH CustomUser::ReportIncorrectVersion(), aFileName( %S )", 
			&aFileName );
	
	switch ( aLogOption )
		{
		case EATLogToFile:
			{
			LOGSTR1( "ATMH ReportIncorrectVersion > EATLogToFile" );			
			
			// A handle to a file server session.
			RFs fs;
			// Creates and opens a file, 
			// and performs all operations on a single open file.
			RFile file;	
			// Create full path buffer
			TBuf<KMaxFileName> logFileBuf;
			// Connects a client to the file server.
			TInt err( fs.Connect() );
			
			if ( !err )
				{				
                err = TATDriveInfo::CreatePath( logFileBuf, aFileName, fs );
			    
				// Replace file if exists
				if ( err && err != KErrAlreadyExists )
					{
					LOGSTR2( "ATMH > TATDriveInfo::CreatePath() err( %i )", err );
					return;
					}
				
				// Replace file if exists (drive C)
				err = file.Replace( fs, logFileBuf, EFileWrite );
										
				// Write to file
				if ( !err )
					{
					err = file.Write( KDataFileVersion );
					// Error msg buffer
					TBuf8<KMaxFileName> msg;				     
					// Write the error code to the buffer  
					logFileBuf.Format( KIncorrectText, &KAtoolApiVersion, &aToolVersion );	
					CnvUtfConverter::ConvertFromUnicodeToUtf8( msg, logFileBuf );
					err = file.Write( msg );
					}
				// Closes the file.
				file.Close();
				}
			
			LOGSTR2( "ATMH > File err( %i )", err );			
			// Closes the handle.
			fs.Close();
			}
			break;
			
		case EATUseDefault:
		case EATLogToTrace:
			{
			LOGSTR1( "ATMH > ReportIncorrectVersion > EATLogToTrace" );
			// Error msg buffer
			TBuf<KMaxFileName> msg;	
			msg.Copy( KIncorrectTextTrace );
			msg.Append( KIncorrectText );
			TBuf<KMaxFileName> traceMsg;	
			// Write the error code to the buffer  
			traceMsg.Format( msg, &KAtoolApiVersion, &aToolVersion );
			RDebug::Print( traceMsg );
			}
			break;
		
		default:
			{
			LOGSTR1( "ATMH > ReportIncorrectVersion > default" );
			}
			break;
		}	
	}

// End of File
