#include <sys/wait.h>
#include "ExecCLITool.h"
#include <Security/utilities.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#pragma mark -------------------- ExecCLITool implementation --------------------
ExecCLITool::ExecCLITool() : dataRead(NULL),dataLength(0),dataToWrite(NULL),dataToWriteLength(0)
{
stdinpipe[0]=0, stdinpipe[1]=0;
stdoutpipe [0]=0, stdoutpipe [1]=0;
}
ExecCLITool::~ExecCLITool()
{
if (dataRead)
free(dataRead);
reset();
}
int ExecCLITool::run(const char *toolPath, const char *toolEnvVar, ...)
{
try
{
reset();
initialize();
switch (pid_t pid = fork())
{
case 0: {
VAArgList arglist;
va_list params;
va_start(params, toolEnvVar);
arglist.set(toolPath,params);
va_end(params);
child(toolPath,toolEnvVar,arglist);
}
break;
case -1: UnixError::throwMe();
break;
default: parent(pid);
break;
}
}
catch (...)
{
closeAllPipes();
return errno;
}
closeAllPipes();
return 0;
}
void ExecCLITool::reset()
{
closeAllPipes();
#if 0
if (dataToWrite)
{
free(dataToWrite);
dataToWrite = NULL;
}
dataToWriteLength = 0;
#endif
}
void ExecCLITool::input(const char *data,unsigned int length)
{
if (dataToWrite)
{
::free(dataToWrite);
dataToWrite = NULL;
}
dataToWriteLength=length;
if (!data)
return;
dataToWrite=reinterpret_cast<char *>(malloc(length));
::memmove(dataToWrite, data, dataToWriteLength);
}
void ExecCLITool::input(CFStringRef theString, bool appendNULL)
{
Boolean isExternalRepresentation = false;
CFStringEncoding encoding = kCFStringEncodingUTF8;
CFIndex usedBufLen = 0;
UInt8 lossByte = 0;
if (!theString)
MacOSError::throwMe(paramErr);
CFRange stringRange = CFRangeMake(0,CFStringGetLength(theString));
CFIndex length = CFStringGetBytes(theString, stringRange, encoding, lossByte,
isExternalRepresentation, NULL, 0, &usedBufLen);
if (dataToWrite)
::free(dataToWrite);
dataToWriteLength=usedBufLen;
if (appendNULL)
{
dataToWriteLength++;
dataToWriteLength++;
}
dataToWrite=reinterpret_cast<char *>(malloc(dataToWriteLength));
length = CFStringGetBytes(theString, stringRange, encoding, lossByte, isExternalRepresentation,
reinterpret_cast<UInt8 *>(dataToWrite), dataToWriteLength, &usedBufLen);
if (appendNULL)
{
dataToWrite[dataToWriteLength-1]=0;
dataToWrite[dataToWriteLength]='\n';
}
}
void ExecCLITool::initialize()
{
dataLength = 0;
if (!dataRead) {
dataRead = (char *)malloc(256);
if (!dataRead)
UnixError::throwMe();
}
if (pipe(stdoutpipe)) UnixError::throwMe();
if (pipe(stdinpipe)) UnixError::throwMe();
}
void ExecCLITool::child(const char *toolPath, const char *toolEnvVar, VAArgList& arglist)
{
try
{
char toolExecutable[PATH_MAX + 1];
const char *path = toolEnvVar ? getenv(toolEnvVar) : NULL;
if (!path)
path = toolPath;
snprintf(toolExecutable, sizeof(toolExecutable), "%s", toolPath);
close(stdoutpipe[0]); close(STDOUT_FILENO);
if (dup2(stdoutpipe[1], STDOUT_FILENO) < 0)
UnixError::throwMe();
close(stdoutpipe[1]);
close(stdinpipe[1]); close(STDIN_FILENO);
if (dup2(stdinpipe[0], STDIN_FILENO) < 0)
UnixError::throwMe();
close(stdinpipe[0]);
execv(toolPath, const_cast<char * const *>(arglist.get()));
}
catch (...)
{
int err = errno;
_exit(err);
}
_exit(1);
}
void ExecCLITool::parent(pid_t pid)
{
static const int timeout = 300;
static const bool dontNeedToWait = false;
close(stdinpipe[0]); close(stdoutpipe[1]);
parentWriteInput();
parentReadOutput();
struct timespec rqtp = {0,};
rqtp.tv_nsec = 100000000; for (int nn = timeout; nn > 0; nanosleep(&rqtp, NULL), nn--)
{
if (dontNeedToWait)
break;
int status;
switch (waitpid(pid, &status, WNOHANG))
{
case 0: break;
case -1: switch (errno)
{
case EINTR:
case EAGAIN: continue;
case ECHILD: CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION);
default:
UnixError::throwMe();
}
default:
return;
}
}
}
void ExecCLITool::parentReadOutput()
{
unsigned int totalRead = 0;
char buffer[kReadBufSize];
for (;;)
{
int thisRead = read(stdoutpipe[0], buffer, kReadBufSize);
if (thisRead < 0)
{
if (errno==EINTR) continue;
break;
}
if (thisRead == 0) {
dataLength = totalRead;
break;
}
if (kReadBufSize < (totalRead + (unsigned int)thisRead))
{
uint32 newLen = dataLength + kReadBufSize;
dataRead = (char *)realloc(dataRead, newLen);
dataLength = newLen;
}
memmove(dataRead + totalRead, buffer, thisRead);
totalRead += thisRead;
}
close(stdoutpipe[0]);
}
void ExecCLITool::parentWriteInput()
{
if (dataToWriteLength>0)
{
int bytesWritten = write(stdinpipe[1],dataToWrite,dataToWriteLength);
if (bytesWritten < 0)
UnixError::throwMe();
}
close(stdinpipe[1]);
}
void ExecCLITool::closeAllPipes()
{
for (int ix=0;ix<2;ix++)
if (stdoutpipe[ix])
{
close(stdoutpipe[ix]);
stdoutpipe[ix]=0;
}
for (int ix=0;ix<2;ix++)
if (stdinpipe[ix])
{
close(stdinpipe[ix]);
stdinpipe[ix]=0;
}
}
#pragma mark -------------------- VAArgList implementation --------------------
int VAArgList::set(const char *path,va_list params)
{
va_list params2;
va_copy(params2, params);
int nn = 1;
while (va_arg(params,const char *) != NULL)
nn++;
argn = nn;
argv = (ArgvArgPtr *)malloc((nn + 1) * sizeof(*argv));
if (argv == NULL)
return 0;
nn = 1;
argv[0]=path;
while ((argv[nn]=va_arg(params2,const char *)) != NULL)
nn++;
mSet = true;
return 0;
}