#include <sys/wait.h>
#include "ExecCLITool.h"
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#if !defined(NDEBUG)
#include <iostream>
#endif
#include <CoreFoundation/CoreFoundation.h>
#include <Security/cssmapple.h>
#pragma mark -------------------- ExecCLITool implementation --------------------
ExecCLITool::ExecCLITool()
{
stdinpipe [READ]=0, stdinpipe [WRITE]=0;
stdoutpipe[READ]=0, stdoutpipe[WRITE]=0;
args.push_back(NULL); }
ExecCLITool::~ExecCLITool()
{
reset();
}
int ExecCLITool::run(const char *toolPath, const char *toolEnvVar, ...)
{
va_list params;
va_start(params, toolEnvVar);
ConstCharPtr theArg;
while ((theArg=va_arg(params,ConstCharPtr)) != NULL)
addarg(theArg);
va_end(params);
return runx(toolPath,toolEnvVar);
}
int ExecCLITool::runx(const char *toolPath, const char *toolEnvVar)
{
int status = 0;
try
{
initializePipes();
switch (pid_t pid = fork())
{
case 0: args[0]=toolPath; addarg(NULL); child(toolPath,toolEnvVar);
break;
case -1: UnixError::throwMe();
break;
default: status = parent(pid);
break;
}
}
catch (...)
{
closeAllPipes();
return errno;
}
closeAllPipes();
return status;
}
void ExecCLITool::reset()
{
closeAllPipes();
cleardata(inputdata);
cleardata(outputdata);
args.erase(args.begin(),args.end());
args.push_back(NULL); }
void ExecCLITool::add(const char *data,unsigned int length,iodatavec& vec)
{
CssmAutoData *tmp = new CssmAutoData(Allocator::standard());
tmp->copy(data,length);
vec.push_back(tmp);
}
void ExecCLITool::cleardata(iodatavec& vec)
{
iodatavec::iterator ix;
for (ix = vec.begin();ix!=vec.end();++ix)
delete *ix;
vec.erase(vec.begin(),vec.end());
}
CFDataRef ExecCLITool::getResults() const
{
CFMutableDataRef results = CFDataCreateMutable(kCFAllocatorDefault, 0);
for (iodatavec::const_iterator ix = outputdata.begin();ix!=outputdata.end();++ix)
{
const CssmAutoData *px = *ix;
CFDataAppendBytes(results, (unsigned char *)px, px->length());
}
return results;
}
void ExecCLITool::addargs(int count,...)
{
ConstCharPtr pArg;
va_list params;
va_start(params, count);
for (int ix=0; (pArg=va_arg(params,ConstCharPtr)) != NULL && ix < count;ix++)
addarg(pArg);
va_end(params);
}
void ExecCLITool::initializePipes()
{
if (pipe(stdoutpipe)) UnixError::throwMe();
if (fcntl(stdoutpipe[READ], F_SETFL, O_NONBLOCK))
UnixError::throwMe();
if (pipe(stdinpipe)) UnixError::throwMe();
}
void ExecCLITool::child(const char *toolPath, const char *toolEnvVar)
{
try
{
char toolExecutable[PATH_MAX + 1];
#if defined(NDEBUG)
const char *path = toolPath;
#else
const char *path = toolEnvVar ? getenv(toolEnvVar) : NULL;
if (!path)
path = toolPath;
#endif
snprintf(toolExecutable, sizeof(toolExecutable), "%s", path);
close(stdoutpipe[READ]); close(STDOUT_FILENO);
if (dup2(stdoutpipe[WRITE], STDOUT_FILENO) < 0)
UnixError::throwMe();
close(stdoutpipe[WRITE]);
close(stdinpipe[WRITE]); close(STDIN_FILENO);
if (dup2(stdinpipe[READ], STDIN_FILENO) < 0)
UnixError::throwMe();
close(stdinpipe[READ]);
execv(toolPath, args.size()?const_cast<char * const *>(&args[0]):NULL); }
catch (...)
{
int err = errno;
_exit(err);
}
_exit(1);
}
int ExecCLITool::parent(pid_t pid)
{
static const int timeout = 2 * 24 * 60 * 60 * 10; static const bool dontNeedToWait = false;
close(stdinpipe[READ]); close(stdoutpipe[WRITE]);
iodatavec::iterator ix;
parentReadOutput();
for (ix = inputdata.begin();ix!=inputdata.end();++ix)
{
parentWriteInput((char *)**ix,(**ix).length());
parentReadOutput();
}
close(stdoutpipe[READ]);
close(stdinpipe[WRITE]);
struct timespec rqtp = {0,};
rqtp.tv_nsec = 100000000; for (int nn = timeout; nn > 0; nanosleep(&rqtp, NULL), nn--)
{
if (dontNeedToWait)
break;
int status = 0;
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 WEXITSTATUS(status);
}
}
return 0; }
void ExecCLITool::parentReadOutput()
{
unsigned int totalRead = 0, dataLength = 0;
char buffer[kReadBufSize];
char *dataRead = (char *)malloc(kReadBufSize);
for (;;)
{
int thisRead = read(stdoutpipe[READ], buffer, kReadBufSize);
if (thisRead < 0)
{
if (errno==EINTR) continue;
else
if (errno==EAGAIN) dataLength = totalRead;
else
debugdisp("abnormal read end:"); break;
}
if (thisRead == 0) {
dataLength = totalRead;
eofSeen[0] = true;
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;
}
if (dataLength)
addout(dataRead,dataLength);
if (dataRead)
::free(dataRead);
}
void ExecCLITool::parentWriteInput(const char *data,unsigned int length)
{
if (length>0)
{
int bytesWritten = write(stdinpipe[WRITE],data,length);
if (bytesWritten < 0)
UnixError::throwMe();
}
}
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 -------------------- hdiutil specific --------------------
void ExecCLITool::addincfs(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 sz = CFStringGetBytes(theString, stringRange, encoding, lossByte,
isExternalRepresentation, NULL, 0, &usedBufLen);
unsigned int length = appendNULL?sz+2:sz;
if (sz>0)
{
auto_ptr<char> data(new char[length]);
char *pdata=data.get();
sz = CFStringGetBytes(theString, stringRange, encoding, lossByte, isExternalRepresentation,
reinterpret_cast<UInt8 *>(pdata), length, &usedBufLen);
if (appendNULL)
{
pdata[length-2]=0;
pdata[length-1]='\n';
}
addin(pdata,length);
}
}
#pragma mark -------------------- Debugging functions --------------------
void ExecCLITool::dump(iodatavec& vec)
{
unsigned char buffer[1024];
iodatavec::iterator ix;
for (ix = vec.begin();ix!=vec.end();++ix)
{
::memcpy(buffer,(unsigned char *)**ix,(**ix).length());
buffer[(**ix).length()]=0;
debugdisp(reinterpret_cast<char *>(buffer));
}
}
void ExecCLITool::debugdisp(const char *msg)
{
#if !defined(NDEBUG)
std::cout << msg << std::endl;
#endif
}