//=================================================================
//
//        eCosTest.cpp
//
//        Test class
//
//=================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class abstracts a test for use in the testing infrastructure.
//                This file contains windows-specific implementations
// Usage:
//
//####DESCRIPTIONEND####

#include "stdafx.h"
#include "eCosTest.h"
#include "X10.h"
#include "eCosTestUtils.h"

int CeCosTest::ReadPipe (void *Handle,CeCosTestUtils::String &str,bool bBlock)
{	
	HANDLE hrData=(HANDLE)Handle;

	DWORD dwAvail;
	int rc=-1;
    if(PeekNamedPipe (hrData, NULL, 0, NULL, &dwAvail, NULL)){
        if(0==dwAvail){
            if(bBlock){
                dwAvail=80;
            } else {
                return 0;
            }
        }
		DWORD dwRead;
		char *buf=str.GetBuffer(dwAvail);
		if(ReadFile (hrData, buf, dwAvail, &dwRead, NULL)){
			buf[dwRead]='\0';
			rc=dwRead;
        }
        str.ReleaseBuffer();
    }
	return rc;
}

bool CeCosTest::WritePipe (void *Handle,const char *pszBuf)
{
	HANDLE hwCmd=(HANDLE)Handle;
	int nToWrite=strlen(pszBuf);
	DWORD dwWritten;
	const char *c=pszBuf;
	do {
		if(!WriteFile(hwCmd,c,nToWrite,&dwWritten,0) || !CheckForTimeout()){
            return false;
        }
		nToWrite-=(int)dwWritten;
		c+=(int)dwWritten;
	} while (nToWrite>0);
	return true;
}

void CeCosTest::RunGdb(const CeCosTestUtils::String &strExec, const CeCosTestUtils::String *arpszCmds)
{

    SetPath(m_strPath);

	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	SECURITY_ATTRIBUTES sa;
	sa.nLength=sizeof sa;
	sa.lpSecurityDescriptor=NULL;
	sa.bInheritHandle=TRUE;
	HANDLE hrData=NULL;
	HANDLE hwCmd=NULL;

	memset(&si,0,sizeof si);
	si.cb=sizeof si;
	si.dwFlags=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
	si.wShowWindow=SW_HIDE;

    CeCosTestUtils::String strCmdline;

    strCmdline.Format("%s -nw %s",(const char *)strExec,(const char *)CygPath(m_strExecutable));

	TRACE("RunGdb '%s'\n",(const char *)strCmdline);


    bool rcCreate=false;
    if(CreatePipe (&si.hStdInput, &hwCmd, &sa, 80)){
		if(CreatePipe (&hrData, &si.hStdOutput, &sa, 80)){
			if(DuplicateHandle (GetCurrentProcess(), si.hStdOutput, GetCurrentProcess(), &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS)){
				if(CreateProcess(NULL,strCmdline.GetBuffer(),NULL,NULL,TRUE,CREATE_NEW_PROCESS_GROUP|NORMAL_PRIORITY_CLASS,NULL,"\\",&si,&pi)){

                    TRACE("Create process '%s' pid=%d\n",(const char *)strCmdline,pi.dwProcessId);

			        rcCreate=true;
					// Process is created - gather its output
					m_pGdbProcesshandle=(void *)(pi.hProcess);
                    m_rPipeHandle=hrData;
                    m_wPipeHandle=hwCmd;
                    
                    m_bDriveGdbComplete=false;

                    // For some unknown reason, PeekNamedPipe returns 0 even when a read will succeed.  Since overlapped
                    // IO is not supported on anonymouse pipes, we are then reduced to using blocking reads under win32.

                    // We must reset all the timing-related variables here, lest the thread doesn't get to do it before the 
                    // first call of CheckForTimeout() below.

                    m_nMaxInactiveTime=0;
                    m_nDownloadTime=0;
                    m_tWallClock0=CeCosTestUtils::Time::Now();
					m_tBase=GdbTime(); 

                    HANDLE hThread=RunThread(DriveGdb,(void *)arpszCmds,Callback(0,&m_bDriveGdbComplete));
                    
                    while(!m_bDriveGdbComplete){
                        CeCosTestUtils::Sleep(250);
      				    if(!GdbProcessAlive()){
                            if(!CeCosTestUtils::WaitFor(m_bDriveGdbComplete,1*1000)){
                                LogString("*** gdb has died unexpectedly");
                                Trace("RunGdb - terminating thread\n");
                                TerminateThread(hThread,1);
                                break;
                            }
                        } else if(!CheckForTimeout()){
                            LogTimeStampedOutput("^C\n");
                            BOOL rc=GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,pi.dwProcessId);
                            Trace("RunGdb - GenerateConsoleCtrlEvent(1) rc=%d\n",rc);
                            if(CeCosTestUtils::WaitFor(m_bDriveGdbComplete,10*1000)){
                                goto DriveGdbDone;
                            }
                            /* // Closing the handles is capable of hanging
                            Trace("RunGdb - Closing handles\n");
                            CloseHandle(hrData); // Force any pending read to complete
                            hrData=0;
                            CloseHandle(hwCmd);  // Force any pending write to complete
                            hwCmd=0;
                            Trace("Handles closed\n");
                            if(!CeCosTestUtils::WaitFor(m_bDriveGdbComplete,10*1000)){
                                goto DriveGdbDone;
                            }
                            */
         				    if(GdbProcessAlive()){
                                Trace("RunGdb - terminating(1) process %d\n",pi.dwProcessId);
                                BOOL rc=TerminateProcess((HANDLE)m_pGdbProcesshandle,1);
                                Trace("rc=%d\n",rc);
                                if(CeCosTestUtils::WaitFor(m_bDriveGdbComplete,10*1000)){
                                    goto DriveGdbDone;
                                }
                            }
                            Trace("RunGdb - terminating thread\n");
                            TerminateThread(hThread,1);
                            break;
                        }
                    }
DriveGdbDone:                    
                    Trace("DriveGdbDone\n");
 				    if(GdbProcessAlive()){
                        Trace("DriveGdbDone - s1\n");
						Suck();
						if(GdbProcessAlive()){
                            Trace("DriveGdbDone - s2\n");
                            if(!AtGdbPrompt()){
                                BOOL rc=GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT,pi.dwProcessId);
                                Trace("RunGdb - GenerateConsoleCtrlEvent(2) rc=%d\n",rc);
								Suck();
                            }
                            if(hwCmd && AtGdbPrompt()){
                                LogString("q\n");
								DWORD dwWritten;
								WriteFile(hwCmd,"q\n",2,&dwWritten,0);

                                Trace("DriveGdbDone - s3\n");
								Suck();
                                
                                if(AtGdbPrompt()){
                                    LogString("y\n");
								    WriteFile(hwCmd,"y\n",2,&dwWritten,0);
                            
                                    Trace("DriveGdbDone - s4\n");
							    	Suck();
                                }
							}
							
							if(GdbProcessAlive()){
                                Trace("RunGdb - terminating(2) process %d\n",pi.dwProcessId);
                                TerminateProcess((HANDLE)m_pGdbProcesshandle,1);
							}
						}
					}
					CloseHandle(pi.hProcess);
					CloseHandle(pi.hThread);
				}
				CloseHandle(si.hStdError);
			}
			CloseHandle(si.hStdOutput);
            if(hrData){
			    CloseHandle(hrData);
            }
		}
		CloseHandle(si.hStdInput);
        if(hwCmd){
            CloseHandle(hwCmd);
        }
	}

	if(!rcCreate){ // failed to create process
		Log("Failed to create gdb process: cmdline='%s'\n",(const char *)strCmdline);
		char *pszMsg;
		
		FormatMessage( 
			FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			NULL,
			GetLastError(),
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
			(LPTSTR)&pszMsg,
			0,
			NULL 
		);

		// Display the string.
		Log("%s\n",pszMsg);

		// Free the buffer.
		LocalFree(pszMsg);
	}
	
}

bool CeCosTest::GdbProcessAlive()
{
	DWORD dwExitRc;
	GetExitCodeProcess((HANDLE)m_pGdbProcesshandle,&dwExitRc);
	return STILL_ACTIVE==dwExitRc;
}

void CeCosTest::LowerGdbPriority()
{
	SetPriorityClass((HANDLE)m_pGdbProcesshandle,IDLE_PRIORITY_CLASS);
}

CeCosTestUtils::Time CeCosTest::GdbCpuTime()
{
	HANDLE hProcess=(HANDLE)m_pGdbProcesshandle;
	__int64 ftCreation,ftExit,ftKernel,ftUser;
	if(NULL!=hProcess && GetProcessTimes (hProcess,(FILETIME *)&ftCreation,(FILETIME *)&ftExit,(FILETIME *)&ftKernel,(FILETIME *)&ftUser)){
		return CeCosTestUtils::Time((int)((ftKernel+ftUser)/10000));
	} else {
        return 0;
	}
}

void CeCosTest::GetPath(CeCosTestUtils::String &strPath)
{
	int nSize=GetEnvironmentVariable("PATH", NULL, 0);
	if(nSize>0){
		GetEnvironmentVariable("PATH", strPath.GetBuffer(nSize), nSize);
		strPath.ReleaseBuffer();
	} else {
		strPath="";
	}
}

void CeCosTest::SetPath(const CeCosTestUtils::String &strPath)
{
	SetEnvironmentVariable("PATH", strPath);
}
