host/common/inmcommand.h (389 lines of code) (raw):
#ifndef INMCOMMAND_H
#define INMCOMMAND_H
//Bug #4044
// DESCRIPTION: Command class to run system command and capture the output,
// error and the exitcode
// Usage:
// 1. Create object passing in the command to be executed. For e.g
//
// InmCommand executable(command);
//
// 2. Two usage scenarios are available:
// a. You can do a indefinite wait for the completion of the command.
//
// InmCommand::statusType status = executable.Run();
//
// Here, the Run returns only when the command finishes execution.
// (or fails to execute)
//
// b. You can do a non-blocking wait, which doesn't block when the command
// doesn't complete immediately
//
// executable.SetShouldWait(false);
// InmCommand::statusType status = executable.Run();
// InmCommand::statusType status = executable.TryWait();
//
// In this case the status returned will be InmCommand::running when
// the command hasn't finished execution.
//
// It returns InmCommand::complete when the command finishes execution.
//
// 3. Collection of ExitCode, Output, Error
//
// a. int exitstatus = executable.ExitCode();
// b. std::string output = executable.StdOut();
// c. std::string error = executable.StdErr();
//
#include <cstdlib>
#include <sstream>
#include <string>
#include <iostream>
#include <ace/Flag_Manip.h>
#include <ace/ACE.h>
#include <ace/Log_Msg.h>
#include <ace/Process.h>
#include <ace/OS.h>
#include <ace/OS_NS_fcntl.h>
#include <ace/OS_NS_unistd.h>
#include <ace/OS_NS_errno.h>
#include <ace/Global_Macros.h>
#ifdef WIN32
#include <windows.h>
#include <Tlhelp32.h>
#endif
inline void INMCOMMAND_SAFE_CLOSEFILEHANDLE(ACE_HANDLE& h)
{
if (ACE_INVALID_HANDLE != h)
{
ACE_OS::close(h);
}
h = ACE_INVALID_HANDLE;
}
class InmCommand
{
public:
enum statusType
{
not_started,
running,
completed,
internal_error,
deleted
};
/*
* FUNCTION NAME : TryWait
*
* DESCRIPTION : Performs a Non-blocking Wait.
* If the command finishes execution, collects the output, error, exitcode
* and returns InmCommand::completed.
* Else returns InmCommand::running
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : statusType such as not_started, running, completed, internal_error
*
*/
statusType TryWait(void)
{
if (m_status != running)
{
return m_status;
}
ACE_exitcode exitcode;
ACE_OS::last_error(0);
pid_t rc = WaitHard(exitcode, WNOHANG);
if (rc == m_pid)
{
#if !defined(ACE_WIN32)
if (WIFEXITED(exitcode) || WEXITSTATUS(exitcode))
{
m_exitstatus = WEXITSTATUS(exitcode);
}
else if (WIFSIGNALED(exitcode))
{
int signo = WTERMSIG(exitcode);
std::ostringstream msg;
msg << m_command << " was signaled by signal# " << WTERMSIG(exitcode);
m_stderr = msg.str();
}
#else
m_exitstatus = exitcode;
#endif /*ACE_WIN32*/
m_status = completed;
m_opt.release_handles();
ReadPipes();
ACE_OS::last_error(0);
if (ACE_OS::close(m_fdout[0]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nTryWait():internal_error: close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
ACE_OS::close(m_fderr[0]);
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
return m_status;
}
if (ACE_OS::close(m_fderr[0]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nTryWait():internal_error: close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
return m_status;
}
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
}
else if (rc == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nTryWait():internal_error: wait failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
return m_status;
}
return m_status;
}
/*
* FUNCTION NAME : Run
*
* DESCRIPTION : Executes the command and optionally waits for the termination status,
* if m_shouldWait is true
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : statusType such as completed, internal_error, running
*
*/
statusType Run(void)
{
ACE_OS::last_error(0);
if (m_status != not_started)
{
return internal_error;
}
if ((ACE_OS::pipe(m_fdout) == -1) || (ACE_OS::pipe(m_fderr) == -1))
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nRun():internal_error: ACE_OS::pipe failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
return m_status;
}
ACE::set_flags(m_fderr[1], ACE_NONBLOCK);
ACE::set_flags(m_fderr[0], ACE_NONBLOCK);
ACE::set_flags(m_fdout[0], ACE_NONBLOCK);
if (!m_isPipeBlocking)
{
ACE::set_flags(m_fdout[1], ACE_NONBLOCK);
}
m_opt.command_line("%s", m_command.c_str());
m_opt.set_handles(ACE_INVALID_HANDLE, m_fdout[1], m_fderr[1]);
ACE_OS::last_error(0);
if ((m_pid = m_proc.spawn(m_opt)) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nRun():internal_error: spawn failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
return m_status;
}
m_status = running;
ACE_OS::last_error(0);
if (ACE_OS::close(m_fdout[1]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nRun():internal_error: close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
ACE_OS::close(m_fderr[1]);
m_fdout[1] = m_fderr[1] = ACE_INVALID_HANDLE;
return m_status;
}
if (ACE_OS::close(m_fderr[1]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nRun():internal_error: close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
m_fdout[1] = m_fderr[1] = ACE_INVALID_HANDLE;
return m_status;
}
m_fdout[1] = m_fderr[1] = ACE_INVALID_HANDLE;
if (m_shouldWait)
{
return Wait();
}
return m_status;
}
/*
* FUNCTION NAME : Terminate
*
* DESCRIPTION : Terminates the command(child process) that was spawned.
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES : It sets the status to InmCommand::internal_error
*
* return value : None
*
*/
void Terminate(void)
{
#ifdef ACE_WIN32
HANDLE command = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == command) {
return;
}
PROCESSENTRY32 processEntry;
if (Process32Next(command, &processEntry)) {
do {
if (processEntry.th32ParentProcessID == m_pid) {
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID);
if (INVALID_HANDLE_VALUE == process) {
break;
}
TerminateProcess(process, 0);
break;
}
} while (Process32Next(command, &processEntry));
}
CloseHandle(command);
#endif
m_proc.terminate();
}
/*
* FUNCTION NAME : ExitCode
*
* DESCRIPTION : Returns the termination status(exit code) of the command
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : Exit code of the command if the command is completed
* else -1
*/
int ExitCode(void)
{
// If the status is completed or internal_error
// proper exit status(actual exit status from command)
// or error because of syscall failure is returned.
// in all other cases -100 is returned.
return m_exitstatus;
}
/*
* FUNCTION NAME : StdOut
*
* DESCRIPTION : Returns the data the command has written to standard output
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS :None
*
* NOTES :
*
* return value : stdout of command if the command is completed
* else ""
*/
std::string StdOut(void)
{
if (m_status == completed)
{
return m_stdout;
}
return "";
}
/*
* FUNCTION NAME : StdErr
*
* DESCRIPTION : Returns the data the command has written to standard error
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS :None
*
* NOTES :
*
* return value : stderr of the command if the command is completed of failed
* else ""
*/
std::string StdErr(void)
{
if (m_status == completed || m_status == internal_error)
{
return m_stderr;
}
return "Command is neither completed nor it encountered any internal error.";
}
/*
* FUNCTION NAME : PutEnv
*
* DESCRIPTION : Sets the environment variables for the command
*
* INPUT PARAMETERS : environment variable string of the form PATH=/usr/local/bin
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : None
*
*/
void PutEnv(const std::string& env)
{
// PR#10815: Long Path support
m_opt.setenv(ACE_TEXT_CHAR_TO_TCHAR(env.c_str()));
}
/*
* FUNCTION NAME : SetOutputSize
*
* DESCRIPTION : Sets the size of the output buffer
*
* INPUT PARAMETERS : size
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : None
*
*/
void SetOutputSize(size_t size)
{
m_outputsize = size;
}
/*
* FUNCTION NAME : SetErrorSize
*
* DESCRIPTION : Sets the size of the error buffer
*
* INPUT PARAMETERS : size
*
* OUTPUT PARAMETERS :None
*
* NOTES :
*
* return value : None
*
*/
void SetErrorSize(size_t size)
{
m_errorsize = size;
}
void SetShouldWait(bool val) { m_shouldWait = val; }
statusType Status(void) { return m_status; };
bool GetShouldWait(void) { return m_shouldWait; }
/*
* FUNCTION NAME : ReadPipes
*
* DESCRIPTION : Reads the output and error streams
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : None
*
*/
void ReadPipes(void)
{
int outpipeIntact = 1;
int errpipeIntact = 1;
while ((m_status == completed || m_status == running) &&
(outpipeIntact || errpipeIntact))
{
if (pollPipe(m_fdout[0], &outpipeIntact, m_stdout, m_outputsize) ||
pollPipe(m_fderr[0], &errpipeIntact, m_stderr, m_errorsize))
{
break;
}
}
}
/*
* FUNCTION NAME : InmCommand
*
* DESCRIPTION : Constructor. Initializes the command name to be executed.
*
* INPUT PARAMETERS : command string
*
* OUTPUT PARAMETERS :None
*
* NOTES :
*
* return value : None
*
*/
InmCommand(const std::string& com, bool blockingPipe = false)
: m_command(com), m_pid(0),
m_status(not_started), m_exitstatus(-100), m_shouldWait(true),
m_outputsize(1024 * 1024), m_errorsize(1024 * 1024),
m_isPipeBlocking(blockingPipe)
{
m_fdout[0] = m_fdout[1] = m_fderr[0] = m_fderr[1] = ACE_INVALID_HANDLE;
}
/*
* FUNCTION NAME : ~InmCommand
*
* DESCRIPTION : Destructor. closes the descriptors if they are open
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS :None
*
* NOTES :
*
* return value : None
*
*/
~InmCommand()
{
assert(m_status == not_started || m_status == completed || m_status == internal_error);
assert(m_status != deleted);
INMCOMMAND_SAFE_CLOSEFILEHANDLE(m_fdout[0]);
INMCOMMAND_SAFE_CLOSEFILEHANDLE(m_fderr[0]);
INMCOMMAND_SAFE_CLOSEFILEHANDLE(m_fdout[1]);
INMCOMMAND_SAFE_CLOSEFILEHANDLE(m_fderr[1]);
m_status = deleted;
}
private:
InmCommand();
InmCommand(const InmCommand&);
void operator=(const InmCommand&);
/*
* FUNCTION NAME : Wait
*
* DESCRIPTION : Performs a blocking wait. Returns when the command finishes execution
* Collects the output, error, exitcode.
*
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : None
*
* NOTES :
*
* return value : statusType such as not_started, commpleted, internal_error
*
*/
statusType Wait(void)
{
if (m_status != running)
{
return m_status;
}
ACE_OS::last_error(0);
ACE_exitcode exitcode = 0;
pid_t rv = WaitHard(exitcode);
if (rv == m_pid)
{
#if !defined(ACE_WIN32)
if (WIFEXITED(exitcode) || WEXITSTATUS(exitcode))
{
m_exitstatus = WEXITSTATUS(exitcode);
}
else if (WIFSIGNALED(exitcode))
{
int signo = WTERMSIG(exitcode);
std::ostringstream msg;
msg << m_command << " was signaled by signal# " << WTERMSIG(exitcode);
m_stderr = msg.str();
}
#else
m_exitstatus = exitcode;
#endif /*ACE_WIN32*/
m_status = completed;
m_opt.release_handles();
ReadPipes();
if (ACE_OS::close(m_fdout[0]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nWait():internal_error:close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
ACE_OS::close(m_fderr[0]);
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
return m_status;
}
if (ACE_OS::close(m_fderr[0]) == -1)
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nWait():internal_error:close failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
return m_status;
}
m_fdout[0] = m_fderr[0] = ACE_INVALID_HANDLE;
}
else
{
m_status = internal_error;
std::ostringstream msg;
m_exitstatus = ACE_OS::last_error();
msg << "errno = " << m_exitstatus << "\nWait():internal_error:wait failed.\nerror = " << ACE_OS::strerror(m_exitstatus);
m_stderr = msg.str();
return m_status;
}
return m_status;
}
/*
* FUNCTION NAME : WaitHard
*
* DESCRIPTION : Performs a blocking wait.
* If wait returns because of EINTR,
* waits again.
*
* Used by InmCommand::Wait, InmCommand::TryWait
*
* INPUT PARAMETERS : None
*
* OUTPUT PARAMETERS : exitcode
*
* NOTES :
*
* return value : pid of the process
*
*/
pid_t WaitHard(ACE_exitcode& exitcode, int options = 0)
{
pid_t rv = -1;
do
{
ACE_OS::last_error(0);
if (options)
{
rv = m_proc.wait(&exitcode, options);
}
else
{
rv = m_proc.wait(&exitcode);
}
} while ((rv != m_pid) && (ACE_OS::last_error() == EINTR));
return rv;
}
/*
* FUNCTION NAME : pollPipe
*
* DESCRIPTION : Reads any data on fd and appends it to buffer. The flag
* is cleared once the other end of the pipe is closed.
*
* INPUT PARAMETERS : i. Handle from which data is to be read
* ii. flag to track when the other end of the pipe is closed
* iii.size limit of the data
*
* OUTPUT PARAMETERS : i. buffer to store the data
* ii. flag to track when the other end of the pipe is closed
*
* NOTES :
*
* return value : 1 if the buffer limit has been reached.
* 0 if there is still room in the buffer.
*
*/
int pollPipe(ACE_HANDLE fd, int *flag,
std::string & buffer, size_t limit)
{
if (!*flag)
{
return 0;
}
const size_t BUFFSIZE = 1024;
char buf[BUFFSIZE + 1];
ssize_t n = 0;
ACE_OS::last_error(0);
n = ACE_OS::read(fd, buf, BUFFSIZE);
if (ACE_OS::last_error())
{
*flag = 0;
return 0;
}
if (!n) //end of file on a pipe
{
*flag = 0;
return 0;
}
if (n > 0)
{
buf[n] = '\0';
buffer.append(buf);
return (buffer.size() > limit);
}
return 0;
}
private:
std::string m_command;
size_t m_outputsize;
size_t m_errorsize;
ACE_HANDLE m_fdout[2];
ACE_HANDLE m_fderr[2];
bool m_isPipeBlocking;
ACE_Process m_proc;
ACE_Process_Options m_opt;
pid_t m_pid;
bool m_shouldWait;
statusType m_status;
int m_exitstatus;
std::string m_stdout;
std::string m_stderr;
};
#endif