host/common/unix/portablemajor.cpp (402 lines of code) (raw):
#include <stdio.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <new>
#include <algorithm>
#include <fcntl.h>
#include "svtypes.h"
#include "portable.h"
#include "svutils.h"
#include "../config/svconfig.h"
#include "../log/logger.h"
#include "portablemajor.h"
#include "version.h"
#include "hostagenthelpers_ported.h"
#include "svutils.h"
#include "inmsafeint.h"
#include "inmageex.h"
static int ACTIVE_PROCESS = -99999;
static int EXIT_PROCESS_GROUP_LEADER_FAILED = -126;
char** SplitArguments( char const* pszCommandLine, int* pArgc = 0 );
SVERROR CProcess::Create( char const* pszCommandLine, CProcess** ppProcess,
char const* pszStdoutLogfile, char const* pszDirName,void* userToken, bool binherithandles,
SV_LOG_LEVEL logLevel)
{
if( NULL == pszCommandLine || NULL == ppProcess )
{
return( SVE_INVALIDARG );
}
/* TODO: handle binherithandles later */
SVERROR rc;
*ppProcess = new (std::nothrow) CUnixProcess(
pszCommandLine, &rc, pszStdoutLogfile, pszDirName, logLevel);
if( NULL == *ppProcess )
{
return( SVE_OUTOFMEMORY );
}
if( rc.failed() )
{
SAFE_DELETE( *ppProcess );
}
return( rc );
}
CUnixProcess::CUnixProcess( char const* pszCommandLine, SVERROR* pErr,
char const* pszStdoutLogfile, char const* pszDirName, SV_LOG_LEVEL logLevel):m_alreadyExited(false),
m_logLevel(logLevel)
{
assert( NULL != pszCommandLine && NULL != pErr );
m_uExitCode = ACTIVE_PROCESS;
*pErr = SVS_OK;
*pErr = CreateSVProcess( pszCommandLine, pszStdoutLogfile );
}
CUnixProcess::~CUnixProcess()
{
}
bool CUnixProcess::hasExited()
{
if(m_alreadyExited) return true;
waitForExit(0);
DebugPrintf(m_logLevel, "[CUnixProcess::hasExited()] Exit Code = %d\n",m_uExitCode);
return (ACTIVE_PROCESS == m_uExitCode) ? false : true;
}
void CUnixProcess::waitForExit( SV_ULONG waitTime )
{
int status = 0;
pid_t pid;
int count = 0;
if(m_alreadyExited) return ;
int sec = waitTime/1000; /* Convert milliseconds to seconds */
DebugPrintf(SV_LOG_DEBUG, "[CUnixProcess::waitForExit()] Going to wait for child process, %d\n",m_childPID);
pid = waitpid(m_childPID,&status,WNOHANG);
DebugPrintf(m_logLevel, "[CUnixProcess::waitForExit()] pid from waitpid()= %d\n",pid);
while(pid == 0 && count < sec)
{
sleep(1);
count++;
pid = waitpid(m_childPID,&status,WNOHANG);
DebugPrintf(SV_LOG_DEBUG, "[CUnixProcess::waitForExit()] pid from waitpid()= %d\n",pid);
}
if(pid != 0)
{
DebugPrintf(m_logLevel, "[CUnixProcess::waitForExit()] Wait over for child process to finish,exit code = %d\n",status);
m_uExitCode = status;
m_alreadyExited = true;
}
else
{
DebugPrintf(m_logLevel, "[CUnixProcess::waitForExit()] Wait over..Child process did not finish, pid = %d\n",m_childPID);
}
}
SVERROR CUnixProcess::terminateWithForce()
{
SVERROR rc;
SVERROR hr = SVS_OK;
//try to kill existing child processes, if any
SVConfig* conf = SVConfig::GetInstance();
std::string value = " ";
hr = conf->GetValue(KEY_KILL_CHIDREN_SCRIPT_PATH,value);
if(!hr.failed() )
{
DebugPrintf(m_logLevel,"[CUnixProcess::terminateWithForce()] Kill children script path = %s\n",value.c_str());
if(value.size() > 0)
{
value += " ";
char strProcessID[10];
memset(strProcessID,0,10);
inm_sprintf_s( strProcessID, ARRAYSIZE(strProcessID), "%d",m_childPID);
value += strProcessID;
trim(value," \n\b\t\a\r\xc");
value += "\0";
DebugPrintf(m_logLevel,"[CUnixProcess::terminateWithForce()] Going to execute this command to kill children = %s =\n",value.c_str());
std::string output("");
GetProcessOutput(value.c_str(), output);
}
}
if(-1 == kill(m_childPID,SIGKILL))
{
rc = errno ;
}
else
{
m_alreadyExited = true;
rc = SVS_OK;
}
return rc;
}
//
// Send a TERM signal to process to kill it. Because it is a process group,
// its children should receive a SIGHUP when it exits. If it doesn't respond,
// we use SIGKILL.
//
SVERROR CUnixProcess::terminate()
{
SVERROR rc;
if( hasExited() )
{
rc = SVS_FALSE;
}
else
{
DebugPrintf(m_logLevel, "[CUnixProcess::terminate()] stopping pid %d\n",
m_childPID );
if( kill( -m_childPID, SIGTERM ) < 0 )
{
DebugPrintf(m_logLevel, "[CUnixProcess::terminate()] not responding to SIGTERM: pid %d\n", m_childPID );
rc = terminateWithForce();
}
else
{
DebugPrintf( m_logLevel, "[CUnixProcess::terminate()] stopped %d\n",
m_childPID );
m_alreadyExited = true;
// discard zombie process
int status = 0;
(void) waitpid( m_childPID, &status, 0 );
}
}
return( rc );
}
SVERROR CUnixProcess::getExitCode( SV_ULONG* pExitCode )
{
*pExitCode = WEXITSTATUS(m_uExitCode);
//*pExitCode = m_uExitCode;
if(WIFEXITED(m_uExitCode))
{
//set status to normal termination
DebugPrintf(m_logLevel,"[CUnixProcess::getExitCode()] Normal termination\n");
return SVS_OK;
}
else
{
DebugPrintf(m_logLevel,"[CUnixProcess::getExitCode()] Abnormal termination\n");
return SVS_FALSE;
}
}
SVERROR CUnixProcess::CreateSVProcess( const char* pszCommandLine,
const char* pszStdoutLogfile )
{
struct sigaction ignore,sigintr,sigquit;
sigset_t chldmask, savemask;
ignore.sa_handler = SIG_IGN;
sigemptyset(&ignore.sa_mask);
ignore.sa_flags = 0;
char** argv = NULL;
int newStdout = 0;
if(sigaction(SIGINT,&ignore,&sigintr) < 0)
{
return errno;
}
if(sigaction(SIGQUIT,&ignore,&sigquit) < 0)
{
return errno;
}
sigemptyset(&chldmask);
sigaddset(&chldmask, SIGCHLD);
if(sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0 )
{
return errno;
}
// Redirect stdout if required
if( NULL != pszStdoutLogfile )
{
newStdout = open( pszStdoutLogfile, O_APPEND | O_WRONLY, 0666 );
if( newStdout < 0 )
{
return errno;
}
}
m_childPID = fork();
switch(m_childPID)
{
case -1 :
{
//set error message that fork failed
DebugPrintf(SV_LOG_ERROR,"[CUnixProcess::CreateSVProcess()] Error in creating child process, errno = %d\n",errno);
return errno;
}
case 0:
{
DebugPrintf(m_logLevel,"[CUnixProcess::CreateSVProcess()] setting prctl option for child \n");
prctl( PR_SET_PDEATHSIG, SIGKILL );
sigaction(SIGINT,&sigintr,NULL);
sigaction(SIGQUIT,&sigquit,NULL);
sigprocmask(SIG_SETMASK, &savemask, NULL);
pid_t group_id = getpid();
if( setpgid( 0, getpid() ) < 0 ) {
DebugPrintf( SV_LOG_ERROR, "[CUnixProcess::CreateSVProcess()] Couldn't become process group leader\n" );
exit( EXIT_PROCESS_GROUP_LEADER_FAILED );
}
//DebugPrintf(SV_LOG_DEBUG,"[CUnixProcess::CreateSVProcess()] Inside child process, Going to execute command\n");
//DebugPrintf(SV_LOG_DEBUG,"[CUnixProcess::CreateSVProcess()] %s\n",pszCommandLine);
//execlp(pszApplicationName,pszApplicationName,"-c", pszCommandLine,(char *)0);
if( newStdout )
{
// Close stdout
close( 1 );
// Replace stdout with new handle (always takes lowest slot)
dup( newStdout );
// Free the handle's slot
close( newStdout );
//redirect stderr to stdout
if (dup2(1,2) == -1)
DebugPrintf(SV_LOG_ERROR,"[CUnixProcess::CreateSVProcess()]attempt to redirect stderr to stdout failed\n");
}
argv = SplitArguments( pszCommandLine );
if( NULL == argv )
{
DebugPrintf(SV_LOG_ERROR,"[CUnixProcess::CreateSVProcess()] Out of memory\n");
}
else
{
execvp( argv[ 0 ], argv );
}
exit(127);
}
default:
{
//this is parent process
//ignore any signal from childa
if( newStdout )
{
close( newStdout );
}
DebugPrintf(m_logLevel,"[CUnixProcess::CreateSVProcess()]parent pid is %d\n",getpid());
DebugPrintf(m_logLevel,"[CUnixProcess::CreateSVProcess()]child pid is %d\n",m_childPID);
}
}
return SVS_OK;
}
SVERROR CDispatchLoop::Create( CDispatchLoop** ppDispatcher )
{
SVERROR rc;
CDispatchLoop* pDispatcher = new (std::nothrow) CUnixDispatchLoop( &rc );
if( NULL == pDispatcher )
{
return( SVE_OUTOFMEMORY );
}
if( rc.failed() )
{
delete pDispatcher ;
}
else
{
*ppDispatcher = pDispatcher;
}
return( rc );
}
CUnixDispatchLoop::CUnixDispatchLoop( SVERROR* pError )
{
assert( NULL != pError );
m_bQuitting = false;
*pError = SVS_OK;
}
void CUnixDispatchLoop::initialize()
{
}
void CUnixDispatchLoop::dispatch( SV_ULONG delay ) /*delay in seconds*/
{
DebugPrintf(SV_LOG_DEBUG,"[CUnixDispatchLoop::dispatch()] Going to sleep for %d seconds\n",delay/1000);
sleep(delay/1000);
}
const char* GetProductVersion()
{
return( INMAGE_PRODUCT_VERSION_STR );
}
const char* GetDriverVersion()
{
// to date, no driver is available on Unix
return( "none" );
}
char* GetThreadsafeErrorStringBuffer()
{
// BUGBUG: not actually threadsafe on Unix
static char szBuffer[ THREAD_SAFE_ERROR_STRING_BUFFER_LENGTH ];
return( szBuffer );
}
///
/// Returns an array of tokenized strings suitable for exec()
/// "one string" yeilds one entry. Quotes may not be escaped.
// Tabs don't tokenize. The returned array and strings are allocated
/// from the heap. Optionally returns the number of arguments.
///
char** SplitArguments( char const* pszCommandLineArg, int* pArgc )
{
int rc = 0;
int maxArraySize = std::count( pszCommandLineArg,
pszCommandLineArg + strlen( pszCommandLineArg ),
' ' ) + 1;
char** pArgv = NULL;
int argc = 0;
do
{
size_t argvlen;
INM_SAFE_ARITHMETIC(argvlen = InmSafeInt<int>::Type(maxArraySize) + 2, INMAGE_EX(maxArraySize))
pArgv = new char*[argvlen]; // one extra for NULL entry
if( NULL == pArgv )
{
rc = -1;
break;
}
int i = 0;
char const* pszCommandLine = pszCommandLineArg;
char const* pszStart = NULL;
enum { WHITESPACE, UNQUOTED, QUOTED, END } state = WHITESPACE;
while( END != state )
{
char ch = *pszCommandLine;
switch( state )
{
case WHITESPACE:
if( 0 == ch ) { state = END; break; }
if( ' ' == ch ) { break; }
if( '"' == ch )
{ state = QUOTED; pszStart = pszCommandLine + 1; break; }
pszStart = pszCommandLine;
state = UNQUOTED;
break;
case QUOTED: // fallthrough
case UNQUOTED:
if( 0 == ch || ((QUOTED==state) ? '"' : ' ') == ch )
{
int cch = pszCommandLine - pszStart;
size_t arglen;
INM_SAFE_ARITHMETIC(arglen = InmSafeInt<int>::Type(cch) + 1, INMAGE_EX(cch))
char* pszArgument = new char[arglen];
if( NULL == pszArgument )
{
rc = -1;
state = END;
break;
}
inm_memcpy_s(pszArgument, arglen,pszStart, cch);
pszArgument[ cch ] = 0;
pArgv[ argc ] = pszArgument;
argc ++;
state = ch ? WHITESPACE : END;
break;
}
break;
}
pszCommandLine ++;
}
} while( false );
//
// Clean up on failure
//
if( rc < 0 )
{
if( NULL != pArgv )
{
for( int i = 0; i < argc; i++ )
{
if( pArgv[ i ] )
{
delete [] pArgv[ i ];
}
}
delete [] pArgv;
}
pArgv = NULL;
argc = 0;
}
if( NULL != pArgc )
{
*pArgc = argc;
}
pArgv[ argc ] = NULL;
return( pArgv );
}
#ifdef TEST_SPLIT_ARGUMENTS
// Small test driver for SplitArguments()
int main()
{
int argc = 0;
char const* pszCommandLine = "\"one and a half\" two \"three\" g";
char** argv = SplitArguments( pszCommandLine, &argc );
assert( NULL == argv[ argc ] );
printf( "Parsing: %s\n", pszCommandLine );
printf( "argc: %d\n", argc );
for( int i = 0; i < argc; i++ )
{
printf( "arg[%d]: %s\n", i, argv[ i ] );
}
return( 0 );
}
#endif // TEST_SPLIT_ARGUMENTS