diff -r 000000000000 -r 3da2a79470a7 testexecmgmt/ucc/Source/ProcessLibrary/proclib_linux.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testexecmgmt/ucc/Source/ProcessLibrary/proclib_linux.cpp Mon Mar 08 15:04:18 2010 +0800 @@ -0,0 +1,835 @@ +/* +* Copyright (c) 2005-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: +* Switches +* +*/ + + + +/******************************************************************************* + * + * System Includes + * + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/******************************************************************************* + * + * Local Includes + * + ******************************************************************************/ +#include "proclib.h" +#include "../include/standard_unix.h" + + +/******************************************************************************* + * + * Macro Functions + * + ******************************************************************************/ +#ifdef UT10 +#undef fork +#define fork() (-1) +#endif + + +/******************************************************************************* + * + * Definitions + * + ******************************************************************************/ +#define INVALID_PIPE_DESC (-1) +#define INVALID_PID (0) +#define READ_BUFFER_SIZE (128) +#define EXECUTE_PAUSE_PERIOD (100000) + + +/******************************************************************************* + * + * PUBLIC: Constructor + * + ******************************************************************************/ +CAProcess::CAProcess( void ) +{ + // initialise all variables + iCommand = NULL; + iProcessStatus = PS_INIT; + iProcessExitReason = ER_INVALID; + iProcessExitCode = 0; + iPID = INVALID_PID; + iRecordStdOut = false; + iRecordStdErr = false; + iStdInPipe[0] = iStdInPipe[1] = INVALID_PIPE_DESC; + iStdOutPipe[0] = iStdOutPipe[1] = INVALID_PIPE_DESC; + iStdErrPipe[0] = iStdErrPipe[1] = INVALID_PIPE_DESC; + iReadStdoutError = 0; + iReadStderrError = 0; +} + +/******************************************************************************* + * + * PUBLIC: Destructor + * + ******************************************************************************/ +CAProcess::~CAProcess() +{ + // I don't allow the object to be destroyed in the PS_STARTED state + assert( iProcessStatus != PS_STARTED ); + + // clean up any handles + if( iCommand != NULL ) { + delete iCommand; + iCommand = NULL; + } + ClosePipes(); +} + +/******************************************************************************* + * + * SECTION-1: PROCESS CONTROL + * + ******************************************************************************/ + +/******************************************************************************* + * + * PUBLIC: StartProcess + * + ******************************************************************************/ +TCAProcessError CAProcess::StartProcess( const char *aCommand, int *aErrorCode, bool aRecordStdOut, + bool aRecordStdErr, bool aMakeNewProcessGroup ) +{ + int err; + + // check params + assert( aCommand != NULL ); + assert( aErrorCode != NULL ); + *aErrorCode = 0; + + // make sure the process is in the init state + if( iProcessStatus != PS_INIT ) { + return CAE_INVALID_STATE; + } + + // verify that the recorded output is empty -- just checking no silly buggers + assert( iRecordedStdOut.empty() == true ); + assert( iRecordedStdErr.empty() == true ); + + // create the pipes that will be setup as the child process's input and output channels. Make the reading + err = CreatePipes( aErrorCode ); + if( err != CAE_NONE ) { + return (TCAProcessError)err; + } + + // flush all stream before forking (AE: why???) + fflush( NULL ); + + // fork now + iPID = fork(); + + // if an error occurred then return failed + if( iPID == -1 ) { + ClosePipes(); + iPID = INVALID_PID; + *aErrorCode = errno; + return CAE_FORK_FAILED; + } + + // if success and we are the parent then... + if( iPID > 0 ) { + + // Close the write end of the output pipes and the read end of the input pipe + ClosePipeDesc( &(iStdInPipe[0]) ); + ClosePipeDesc( &(iStdOutPipe[1]) ); + ClosePipeDesc( &(iStdErrPipe[1]) ); + + // Update the state + iProcessStatus = PS_STARTED; + iCommand = new string( aCommand ); + iRecordStdOut = aRecordStdOut; + iRecordStdErr = aRecordStdErr; + + // done + return CAE_NONE; + } + + // if success and we are the child then... + if( iPID == 0 ) { + + // if requested - put the process in it's own process group + if( aMakeNewProcessGroup ) { + err = setpgid( 0, 0 ); + if( err != 0 ) { + fprintf( stderr, "WARNING: failed to setpgid for process %d - %s (%d).\n", getpid(), strerror(errno), errno ); + } + } + + // close the write end of stdin and make the read end desc 0 + ClosePipeDesc( &(iStdInPipe[1]) ); + close( stdin->_fileno ); + err = dup( iStdInPipe[0] ); + assert( err == (stdin->_fileno) ); + + // close the read end of stdout and make the write end desc 1 + if( aRecordStdOut ) { + ClosePipeDesc( &(iStdOutPipe[0]) ); + close( stdout->_fileno ); + err = dup( iStdOutPipe[1] ); + assert( err == (stdout->_fileno) ); + } + + // close the read end of stderr and make the write end desc 2 + if( aRecordStdErr ) { + ClosePipeDesc( &(iStdErrPipe[0]) ); + close( stderr->_fileno ); + err = dup( iStdErrPipe[1] ); + assert( err == (stderr->_fileno) ); + } + + // NOTE: A problem exists that because we have inherited all the descriptors + // form our parent, any descriptors that are subsequently closed by the + // parent will still be open until the child exits. + + // exec the target image. There is a choice here of whether to exec the target + // directly or to use /bin/sh. The tradeoff is that /bin/sh gives you argument + // parsing -- but exec will not fail with an invalid target because /bin/sh + // is always available to exec. Currently I'm choosing the former but you + // should be aware of the tradeoff.m + execl("/bin/sh", "csh", "-c", aCommand, NULL); + //execl( aCommand, NULL ); + + // The call to execl() failed. The following error message will be + // sent back as if it came from the process (on stderr). The process + // then exits. + fprintf( stderr, "ERROR: execl() failed with error '%s' (%d)\n", strerror(errno), errno ); + ClosePipeDesc( &(iStdInPipe[0]) ); + ClosePipeDesc( &(iStdOutPipe[1]) ); + ClosePipeDesc( &(iStdErrPipe[1]) ); + exit( -1 ); + } + + // should never get here + assert( !"Invalid code path" ); + return CAE_NONE; +} + + +/******************************************************************************* + * + * PUBLIC: RequestStop + * + ******************************************************************************/ +TCAProcessError CAProcess::RequestStop( int aSignal ) +{ + int err; + + // get the state + err = GetProcessStatus( NULL ); + assert( err == CAE_NONE ); + + // check the state + if( iProcessStatus != PS_STARTED ) { + return CAE_INVALID_STATE; + } + + // send the specified signal to the process + err = kill( iPID, aSignal ); + + // if kill() fails then we abandon the process -- STATE TRANSITION + if( err != 0 ) { + iProcessStatus = PS_ABANDONNED; + iProcessExitReason = ER_SIGNALFAILED; + iProcessExitCode = errno; + ClosePipes(); + return CAE_SIGNAL_FAILED; + } + + // otherwise all is ok + return CAE_NONE; +} + + +/******************************************************************************* + * + * PUBLIC: GetProcessStatus + * + ******************************************************************************/ +TCAProcessError CAProcess::GetProcessStatus( TProcessStatus *aProcessStatus ) +{ + int err; + int status; + + // if the process is in the PS_STARTED state then we have to verify that it is still running + if( iProcessStatus == PS_STARTED ) { + + // see if the process is still running + assert( iPID > 0 ); + err = waitpid( iPID, &status, WNOHANG ); + + // if an error occured then transition from started to abandonned state + if( err == -1 ) { + iProcessStatus = PS_ABANDONNED; + iProcessExitReason = ER_WAITPIDFAILED; + iProcessExitCode = errno; + } + + // if the process is still running then no change + if( err == 0 ) { + } + + // if the process has finished then transition from started to stopped state + if( err > 0 ) { + iProcessStatus = PS_STOPPED; + iProcessExitReason = GetExitReasonFromStatus( status, &iProcessExitCode ); + } + } + + // if the passed pointer is non-null then we return the status + if( aProcessStatus != NULL ) { + *aProcessStatus = iProcessStatus; + } + + // done + return CAE_NONE; +} + + +/******************************************************************************* + * + * PUBLIC: WaitForProcessToTerminate + * + ******************************************************************************/ +TCAProcessError CAProcess::WaitForProcessToTerminate( int aMaxWait ) +{ + int err; + int status; + int i; + + // if the process isn't in the PS_STARTED state then this call is invalid -- note + // that GetProcessStatus is not used to update the status here since if the + // process has stopped we want this to be caught in the waitpid below, not here. + if( iProcessStatus != PS_STARTED ) { + return CAE_INVALID_STATE; + } + + // Wait for the process. We can't just do a blocking wait since the stdout/stderr + // pipes would overflow and people wouldn't be able to read the output as + // expected when the process has terminated. So we do a non-blocking wait in a loop. + for( i = 0; (aMaxWait == -1) || (i < aMaxWait); i++ ) { + + // see if the process has finished + err = waitpid( iPID, &status, WNOHANG ); + if( err != 0 ) { + break; + } + + // see if there is any data to read in from the pipes -- this will transfer the data + // from the pipes to the internal buffers + InternalPollProcessForNewOutput( 0, NULL, NULL ); + } + + // see if we've timedout + if( err == 0 ) { + assert( i == aMaxWait ); + return CAE_TIMEOUT; + } + + // if the call failed otherwise we abandon the process + if( err == -1 ) { + iProcessStatus = PS_ABANDONNED; + iProcessExitReason = ER_WAITPIDFAILED; + iProcessExitCode = errno; + return CAE_WAITPID_FAILED; + } + + // otherwise everything is good and we have stopped the process + assert( err > 0 ); + iProcessStatus = PS_STOPPED; + iProcessExitReason = GetExitReasonFromStatus( status, &iProcessExitCode ); + return CAE_NONE; +} + +/******************************************************************************* + * + * PUBLIC: Execute() + * + ******************************************************************************/ +TCAProcessError CAProcess::Execute( const char *aCommand, int *aErrorCode, int aTimeoutInMilliseconds, string *aStandardOutput, + string *aStandardError ) +{ + TCAProcessError err; + int i, stdout_bytes_read, stderr_bytes_read, errcode, max_iterations, timeout_in_microseconds; + TProcessStatus pstatus; + bool record_std_out, record_std_err; + struct timespec pause_period = { 0, EXECUTE_PAUSE_PERIOD }; + + // validate params + assert( aCommand != NULL ); + assert( aErrorCode != NULL ); + record_std_out = (aStandardOutput != NULL); + record_std_err = (aStandardError != NULL); + + // start the process + err = StartProcess( aCommand, aErrorCode, record_std_out, record_std_err, false ); + if( err != CAE_NONE ) { + return err; + } + + // work out the maximum number of iterations + timeout_in_microseconds = aTimeoutInMilliseconds * 1000; + max_iterations = (timeout_in_microseconds / EXECUTE_PAUSE_PERIOD); + // fprintf( stderr, "DEBUG: timeout_in_micro = %d, pause_period = %d, timeout = %d, max_iterations = %d\n", + // timeout_in_microseconds, EXECUTE_PAUSE_PERIOD, aTimeoutInMilliseconds, max_iterations ); + + // wait for the process to exit - store the output + for( i = 0; (i < max_iterations) || (aTimeoutInMilliseconds == -1); i++ ) { + + // check the status of the process + err = GetProcessStatus( &pstatus ); + assert( err == CAE_NONE ); + + // sanity check on the status + assert( (pstatus != PS_INVALID) && (pstatus != PS_INIT) ); + + // if the status isn't PS_STARTED then we exit + if( pstatus != PS_STARTED ) { + break; + } + + // get any new output - errors are ignored + if( record_std_out || record_std_err ) { + PollProcessForNewOutput( 0, &stdout_bytes_read, &stderr_bytes_read ); + GetRecordedStandardOutput( aStandardOutput, &errcode ); + GetRecordedStandardError( aStandardError, &errcode ); + } + + // now wait for one second and then lets do it all again!! + nanosleep( &pause_period, NULL ); + } + + // get the current status + err = GetProcessStatus( &pstatus ); + assert( err == CAE_NONE ); + + // if we are still running then send a kill signal and wait to exit + if( pstatus == PS_STARTED ) { + err = RequestStop( SIGKILL ); + if( (err != CAE_NONE) && (err != CAE_INVALID_STATE) ) { + return err; + } + err = WaitForProcessToTerminate( -1 ); + if( err != CAE_NONE ) { + return err; + } + } + + // get any last minute output + if( record_std_out || record_std_err ) { + PollProcessForNewOutput( 0, &stdout_bytes_read, &stderr_bytes_read ); + GetRecordedStandardOutput( aStandardOutput, &errcode ); + GetRecordedStandardError( aStandardError, &errcode ); + } + + // we have to return an indication of whether the process timed out + return ((i == max_iterations) ? CAE_TIMEOUT : CAE_NONE); +} + + +/******************************************************************************* + * + * SECTION-2: DATA CONTROL + * + ******************************************************************************/ + +/******************************************************************************* + * + * PUBLIC: PollProcessForNewOutput. Polls the executing process for new output. + * Any output is then transferred to the internal buffers. + * + ******************************************************************************/ +TCAProcessError CAProcess::PollProcessForNewOutput( int aMaxWait, int *aStandardOutputRead, int *aStandardErrorRead ) +{ + GetProcessStatus( NULL ); + if( iProcessStatus == PS_INIT ) { + return CAE_INVALID_STATE; + } + return InternalPollProcessForNewOutput( aMaxWait, aStandardOutputRead, aStandardErrorRead ); +} + +TCAProcessError CAProcess::InternalPollProcessForNewOutput( int aMaxWait, int *aStandardOutputRead, int *aStandardErrorRead ) +{ + struct timeval maxWait; + fd_set readset; + int fdsToProcess; + int is_set; + int err_stdout = 0; + int err_stderr = 0; + + // initialise the out params + if( aStandardOutputRead != NULL ) { + *aStandardOutputRead = 0; + } + if( aStandardErrorRead != NULL ) { + *aStandardErrorRead = 0; + } + + // make sure one of the pipes is still open + if( (iStdOutPipe[0] == INVALID_PIPE_DESC) && (iStdErrPipe[0] == INVALID_PIPE_DESC) ) { + return CAE_NONE; + } + + // Perform a select (of upto the maximum allowed time) and read in the available data. + FD_ZERO( &readset ); + if( iStdOutPipe[0] != INVALID_PIPE_DESC ) { + FD_SET( iStdOutPipe[0], &readset ); + } + if( iStdErrPipe[0] != INVALID_PIPE_DESC ) { + FD_SET( iStdErrPipe[0], &readset ); + } + maxWait.tv_sec = aMaxWait; + maxWait.tv_usec = 0; + fdsToProcess = select( FD_SETSIZE, &readset, NULL, NULL, &maxWait ); + if( fdsToProcess < 0 ) { + iReadStdoutError = iReadStderrError = errno; + return CAE_SELECT_FAILED; + } + + // If there is no data to read then exit + if( fdsToProcess == 0 ) { + return CAE_NONE; + } + + // Read the stdout pipe + if( iStdOutPipe[0] != INVALID_PIPE_DESC ) { + is_set = FD_ISSET( iStdOutPipe[0], &readset ); + if( is_set != 0 ) { + err_stdout = ReadOutput( &(iStdOutPipe[0]), &iRecordedStdOut, iRecordStdOut, &iReadStdoutError ); + if( aStandardOutputRead != NULL ) { + *aStandardOutputRead = iRecordedStdOut.length(); + } + } + } + + // Read the stderr pipe + if( iStdErrPipe[0] != INVALID_PIPE_DESC ) { + is_set = FD_ISSET( iStdErrPipe[0], &readset ); + if( is_set != 0 ) { + err_stderr = ReadOutput( &(iStdErrPipe[0]), &iRecordedStdErr, iRecordStdErr, &iReadStderrError ); + if( aStandardErrorRead != NULL ) { + *aStandardErrorRead = iRecordedStdErr.length(); + } + } + } + + // If one of the reads returned an error then return this + if( (err_stdout != 0) || (err_stderr != 0) ) { + return CAE_READ_FAILED; + } + return CAE_NONE; +} + + +/******************************************************************************* + * + * PUBLIC: GetRecordedStandardOutput + * + ******************************************************************************/ +int CAProcess::GetRecordedStandardOutput( string *aStdout, int *aReadError ) +{ + // validate params + assert( aReadError != NULL ); + if( aStdout == NULL ) { + return 0; + } + + // append a copy of the stored data to the passed string + (*aStdout) += iRecordedStdOut; + + // clear the current string + iRecordedStdOut.erase( 0, iRecordedStdOut.length() ); + + // set the read error + *aReadError = iReadStdoutError; + iReadStdoutError = 0; + + // return the number of bytes read + return aStdout->size(); +} + + +/******************************************************************************* + * + * PUBLIC: GetRecordedStandardError + * + ******************************************************************************/ +int CAProcess::GetRecordedStandardError( string *aStdErr, int *aReadError ) +{ + // validate params + assert( aReadError != NULL ); + if( aStdErr == NULL ) { + return 0; + } + + // append a copy of the stored data to the passed string + (*aStdErr) += iRecordedStdErr; + + // clear the current string + iRecordedStdErr.erase( 0, iRecordedStdErr.length() ); + + // set the read error + *aReadError = iReadStderrError; + iReadStderrError = 0; + + // return the number of bytes read + return aStdErr->size(); +} + + +/******************************************************************************* + * + * SECTION-3: General Accessors + * + ******************************************************************************/ + +/******************************************************************************* + * + * PUBLIC: GetCommandString + * + ******************************************************************************/ +string CAProcess::GetCommandString() +{ + string cmd; + if( iProcessStatus == PS_INIT ) { + return cmd; + } + cmd = *iCommand; + return cmd; +} + +/******************************************************************************* + * + * PUBLIC: GetExitReason + * + ******************************************************************************/ +TCAProcessError CAProcess::GetExitReason( TProcessExitReason *aExitReason ) +{ + // check the state -- otherwise this is not valid + if( (iProcessStatus != PS_STOPPED) && (iProcessStatus != PS_ABANDONNED) ) { + return CAE_INVALID_STATE; + } + + // set the value + if( aExitReason != NULL ) { + *aExitReason = iProcessExitReason; + } + + // done + return CAE_NONE; +} + +/******************************************************************************* + * + * PUBLIC: GetExitCode + * + ******************************************************************************/ +TCAProcessError CAProcess::GetExitCode( int *aExitCode ) +{ + // check the state -- otherwise this is not valid + if( (iProcessStatus != PS_STOPPED) && (iProcessStatus != PS_ABANDONNED) ) { + return CAE_INVALID_STATE; + } + + // set the value + if( aExitCode != NULL ) { + *aExitCode = iProcessExitCode; + } + + // done + return CAE_NONE; +} + + +/******************************************************************************* + * + * SECTION-4: Helpers + * + ******************************************************************************/ + +/******************************************************************************* + * + * PRIVATE: GetExitReasonFromStatus + * + ******************************************************************************/ +TProcessExitReason CAProcess::GetExitReasonFromStatus( int aStatus, int *aExitCode ) +{ + assert( aExitCode != NULL ); + if( WIFEXITED(aStatus) ) { + *aExitCode = WEXITSTATUS(aStatus); + return ER_EXITED; + } + if( WIFSIGNALED(aStatus) ) { + *aExitCode = WTERMSIG(aStatus); + return ER_SIGNALLED; + } + return ER_UNKNOWN; +} + +/******************************************************************************* + * + * PRIVATE: ReadOutput + * + ******************************************************************************/ +TCAProcessError CAProcess::ReadOutput( int *aFileDes, string *aBuffer, bool aStoreFlag, int *aReadError ) +{ + char buff[READ_BUFFER_SIZE]; + int bytesRead; + + // check params + assert( aBuffer != NULL ); + assert( aReadError != NULL ); + assert( aFileDes != NULL ); + + // read until there is nothing left to read + while( true ) { + + // read + bytesRead = read( (*aFileDes), &buff, READ_BUFFER_SIZE-1 ); + + // null-terminate the buffer + if( bytesRead > 0 ) { + buff[bytesRead] = 0; + } + + // temporarily unavailable resource is not an error + if( (bytesRead == -1) && (errno == EAGAIN) ) { + return CAE_NONE; + } + + // check for error + if( bytesRead == -1 ) { + fprintf( stderr, "DEBUG: read() returned error %s\n", strerror(errno) ); + *aReadError = errno; + return CAE_READ_FAILED; + } + + // check for no more data -- if this is the case then close the pipe + if( bytesRead == 0 ) { + ClosePipeDesc( aFileDes ); + return CAE_NONE; + } + + // store the data in the object if requested + if( aStoreFlag ) { + fflush( stderr ); + (*aBuffer) += buff; + } + } + + // should never get here + assert( !"Invalid code path" ); + return CAE_NONE; +} + +/******************************************************************************* + * + * PRIVATE: CreatePipes / ClosePipes / ClosePipePair + * + ******************************************************************************/ +TCAProcessError CAProcess::CreatePipes( int *aErrorCode ) +{ + int err; + int flags; + + // check params + assert( aErrorCode != NULL ); + + // create the stdin pipe + err = pipe( iStdInPipe ); + if( err != 0 ) { + *aErrorCode = errno; + return CAE_FAILED_TO_CREATE_PIPE; + } + + // create the stdout pipe + err = pipe( iStdOutPipe ); + if( err != 0 ) { + ClosePipes(); + *aErrorCode = errno; + return CAE_FAILED_TO_CREATE_PIPE; + } + + // create the stderr pipe + err = pipe( iStdErrPipe ); + if( err != 0 ) { + ClosePipes(); + *aErrorCode = errno; + return CAE_FAILED_TO_CREATE_PIPE; + } + + // make the read end of the stdout pipe non-blocking + flags = fcntl( iStdOutPipe[0], F_GETFL, 0 ); + flags |= O_NONBLOCK; + err = fcntl( iStdOutPipe[0], F_SETFL, flags ); + if( err != 0 ) { + ClosePipes(); + *aErrorCode = errno; + return CAE_FAILED_TO_SET_NONBLOCKING; + } + + // make the read end of the stderr pipe non-blocking + flags = fcntl( iStdErrPipe[0], F_GETFL, 0 ); + flags |= O_NONBLOCK; + err = fcntl( iStdErrPipe[0], F_SETFL, flags ); + if( err != 0 ) { + ClosePipes(); + *aErrorCode = errno; + return CAE_FAILED_TO_SET_NONBLOCKING; + } + + // OK + return CAE_NONE; +} + +void CAProcess::ClosePipes() +{ + ClosePipePair( iStdInPipe ); + ClosePipePair( iStdOutPipe ); + ClosePipePair( iStdErrPipe ); +} + + +void CAProcess::ClosePipePair( int *aPipes ) +{ + assert( aPipes != NULL ); + ClosePipeDesc( &(aPipes[0]) ); + ClosePipeDesc( &(aPipes[1]) ); +} + + +void CAProcess::ClosePipeDesc( int *aPipeDescriptor ) +{ + assert( aPipeDescriptor != NULL ); + close( *aPipeDescriptor ); + *aPipeDescriptor = INVALID_PIPE_DESC; +} +