blob: 226147835c0a8c652af9535bfc4f2166c2025791 [file] [log] [blame]
/*-------------------------------------------------------------------------
* drawElements Utility Library
* ----------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief Process abstraction.
*//*--------------------------------------------------------------------*/
#include "deProcess.h"
#include "deMemory.h"
#include "deString.h"
#if (DE_OS == DE_OS_UNIX) || (DE_OS == DE_OS_OSX) || (DE_OS == DE_OS_IOS) || (DE_OS == DE_OS_ANDROID) || (DE_OS == DE_OS_SYMBIAN) || (DE_OS == DE_OS_QNX)
#include "deCommandLine.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
typedef enum ProcessState_e
{
PROCESSSTATE_NOT_STARTED = 0,
PROCESSSTATE_RUNNING,
PROCESSSTATE_FINISHED,
PROCESSSTATE_LAST
} ProcessState;
struct deProcess_s
{
ProcessState state;
int exitCode;
char* lastError;
pid_t pid;
deFile* standardIn;
deFile* standardOut;
deFile* standardErr;
};
static void die (int statusPipe, const char* message)
{
size_t msgLen = strlen(message);
int res = 0;
printf("Process launch failed: %s\n", message);
res = (int)write(statusPipe, message, msgLen+1);
DE_UNREF(res); /* No need to check result. */
exit(-1);
}
static void dieLastError (int statusPipe, const char* message)
{
char msgBuf[256];
int lastErr = errno;
deSprintf(msgBuf, sizeof(msgBuf), "%s, error %d: %s", message, lastErr, strerror(lastErr));
die(statusPipe, msgBuf);
}
DE_INLINE deBool beginsWithPath (const char* fileName, const char* pathPrefix)
{
size_t pathLen = strlen(pathPrefix);
/* Strip trailing / */
while (pathLen > 0 && pathPrefix[pathLen-1] == '/')
pathLen -= 1;
return pathLen > 0 && deMemoryEqual(fileName, pathPrefix, pathLen) && fileName[pathLen] == '/';
}
static void stripLeadingPath (char* fileName, const char* pathPrefix)
{
size_t pathLen = strlen(pathPrefix);
size_t fileNameLen = strlen(fileName);
DE_ASSERT(beginsWithPath(fileName, pathPrefix));
/* Strip trailing / */
while (pathLen > 0 && pathPrefix[pathLen-1] == '/')
pathLen -= 1;
DE_ASSERT(pathLen > 0);
DE_ASSERT(fileName[pathLen] == '/');
memmove(&fileName[0], &fileName[0]+pathLen+1, fileNameLen-pathLen);
}
/* Doesn't return on success. */
static void execProcess (const char* commandLine, const char* workingDirectory, int statusPipe)
{
deCommandLine* cmdLine = deCommandLine_parse(commandLine);
char** argList = cmdLine ? (char**)deCalloc(sizeof(char*)*((size_t)cmdLine->numArgs+1)) : DE_NULL;
if (!cmdLine || !argList)
die(statusPipe, "Command line parsing failed (out of memory)");
if (workingDirectory && chdir(workingDirectory) != 0)
dieLastError(statusPipe, "chdir() failed");
{
int argNdx;
for (argNdx = 0; argNdx < cmdLine->numArgs; argNdx++)
argList[argNdx] = cmdLine->args[argNdx];
argList[argNdx] = DE_NULL; /* Terminate with 0. */
}
if (workingDirectory && beginsWithPath(argList[0], workingDirectory))
stripLeadingPath(argList[0], workingDirectory);
execv(argList[0], argList);
/* Failed. */
dieLastError(statusPipe, "execv() failed");
}
deProcess* deProcess_create (void)
{
deProcess* process = (deProcess*)deCalloc(sizeof(deProcess));
if (!process)
return DE_FALSE;
process->state = PROCESSSTATE_NOT_STARTED;
return process;
}
static void deProcess_cleanupHandles (deProcess* process)
{
if (process->standardIn)
deFile_destroy(process->standardIn);
if (process->standardOut)
deFile_destroy(process->standardOut);
if (process->standardErr)
deFile_destroy(process->standardErr);
process->pid = 0;
process->standardIn = DE_NULL;
process->standardOut = DE_NULL;
process->standardErr = DE_NULL;
}
void deProcess_destroy (deProcess* process)
{
/* Never leave child processes running. Otherwise we'll have zombies. */
if (deProcess_isRunning(process))
{
deProcess_kill(process);
deProcess_waitForFinish(process);
}
deProcess_cleanupHandles(process);
deFree(process->lastError);
deFree(process);
}
const char* deProcess_getLastError (const deProcess* process)
{
return process->lastError ? process->lastError : "No error";
}
int deProcess_getExitCode (const deProcess* process)
{
return process->exitCode;
}
static deBool deProcess_setError (deProcess* process, const char* error)
{
if (process->lastError)
{
deFree(process->lastError);
process->lastError = DE_NULL;
}
process->lastError = deStrdup(error);
return process->lastError != DE_NULL;
}
static deBool deProcess_setErrorFromErrno (deProcess* process, const char* message)
{
char msgBuf[256];
int lastErr = errno;
deSprintf(msgBuf, sizeof(msgBuf), "%s, error %d: %s", message, lastErr, strerror(lastErr));
return deProcess_setError(process, message);
}
static void closePipe (int p[2])
{
if (p[0] >= 0)
close(p[0]);
if (p[1] >= 0)
close(p[1]);
}
deBool deProcess_start (deProcess* process, const char* commandLine, const char* workingDirectory)
{
pid_t pid = 0;
int pipeIn[2] = { -1, -1 };
int pipeOut[2] = { -1, -1 };
int pipeErr[2] = { -1, -1 };
int statusPipe[2] = { -1, -1 };
if (process->state == PROCESSSTATE_RUNNING)
{
deProcess_setError(process, "Process already running");
return DE_FALSE;
}
else if (process->state == PROCESSSTATE_FINISHED)
{
deProcess_cleanupHandles(process);
process->state = PROCESSSTATE_NOT_STARTED;
}
if (pipe(pipeIn) < 0 || pipe(pipeOut) < 0 || pipe(pipeErr) < 0 || pipe(statusPipe) < 0)
{
deProcess_setErrorFromErrno(process, "pipe() failed");
closePipe(pipeIn);
closePipe(pipeOut);
closePipe(pipeErr);
closePipe(statusPipe);
return DE_FALSE;
}
pid = fork();
if (pid < 0)
{
deProcess_setErrorFromErrno(process, "fork() failed");
closePipe(pipeIn);
closePipe(pipeOut);
closePipe(pipeErr);
closePipe(statusPipe);
return DE_FALSE;
}
if (pid == 0)
{
/* Child process. */
/* Close unused endpoints. */
close(pipeIn[1]);
close(pipeOut[0]);
close(pipeErr[0]);
close(statusPipe[0]);
/* Set status pipe to close on exec(). That way parent will know that exec() succeeded. */
if (fcntl(statusPipe[1], F_SETFD, FD_CLOEXEC) != 0)
dieLastError(statusPipe[1], "Failed to set FD_CLOEXEC");
/* Map stdin. */
if (pipeIn[0] != STDIN_FILENO &&
dup2(pipeIn[0], STDIN_FILENO) != STDIN_FILENO)
dieLastError(statusPipe[1], "dup2() failed");
close(pipeIn[0]);
/* Stdout. */
if (pipeOut[1] != STDOUT_FILENO &&
dup2(pipeOut[1], STDOUT_FILENO) != STDOUT_FILENO)
dieLastError(statusPipe[1], "dup2() failed");
close(pipeOut[1]);
/* Stderr. */
if (pipeErr[1] != STDERR_FILENO &&
dup2(pipeErr[1], STDERR_FILENO) != STDERR_FILENO)
dieLastError(statusPipe[1], "dup2() failed");
close(pipeErr[1]);
/* Doesn't return. */
execProcess(commandLine, workingDirectory, statusPipe[1]);
}
else
{
/* Parent process. */
/* Check status. */
{
char errBuf[256];
ssize_t result = 0;
close(statusPipe[1]);
while ((result = read(statusPipe[0], errBuf, 1)) == -1)
if (errno != EAGAIN && errno != EINTR) break;
if (result > 0)
{
int procStatus = 0;
/* Read full error msg. */
int errPos = 1;
while (errPos < DE_LENGTH_OF_ARRAY(errBuf))
{
result = read(statusPipe[0], errBuf+errPos, 1);
if (result == -1)
break; /* Done. */
errPos += 1;
}
/* Make sure str is null-terminated. */
errBuf[errPos] = 0;
/* Close handles. */
close(statusPipe[0]);
closePipe(pipeIn);
closePipe(pipeOut);
closePipe(pipeErr);
/* Run waitpid to clean up zombie. */
waitpid(pid, &procStatus, 0);
deProcess_setError(process, errBuf);
return DE_FALSE;
}
/* Status pipe is not needed. */
close(statusPipe[0]);
}
/* Set running state. */
process->pid = pid;
process->state = PROCESSSTATE_RUNNING;
/* Stdin, stdout. */
close(pipeIn[0]);
close(pipeOut[1]);
close(pipeErr[1]);
process->standardIn = deFile_createFromHandle((deUintptr)pipeIn[1]);
process->standardOut = deFile_createFromHandle((deUintptr)pipeOut[0]);
process->standardErr = deFile_createFromHandle((deUintptr)pipeErr[0]);
if (!process->standardIn)
close(pipeIn[1]);
if (!process->standardOut)
close(pipeOut[0]);
if (!process->standardErr)
close(pipeErr[0]);
}
return DE_TRUE;
}
deBool deProcess_isRunning (deProcess* process)
{
if (process->state == PROCESSSTATE_RUNNING)
{
int status = 0;
if (waitpid(process->pid, &status, WNOHANG) == 0)
return DE_TRUE; /* No status available. */
if (WIFEXITED(status) || WIFSIGNALED(status))
{
/* Child has finished. */
process->state = PROCESSSTATE_FINISHED;
return DE_FALSE;
}
else
return DE_TRUE;
}
else
return DE_FALSE;
}
deBool deProcess_waitForFinish (deProcess* process)
{
int status = 0;
pid_t waitResult;
if (process->state != PROCESSSTATE_RUNNING)
{
deProcess_setError(process, "Process is not running");
return DE_FALSE;
}
/* \note [pyry] HACK, apparently needed by some versions of OS X. */
while ((waitResult = waitpid(process->pid, &status, 0)) != process->pid)
if (errno != ENOENT) break;
if (waitResult != process->pid)
{
deProcess_setErrorFromErrno(process, "waitpid() failed");
return DE_FALSE; /* waitpid() failed. */
}
if (!WIFEXITED(status) && !WIFSIGNALED(status))
{
deProcess_setErrorFromErrno(process, "waitpid() failed");
return DE_FALSE; /* Something strange happened. */
}
process->exitCode = WEXITSTATUS(status);
process->state = PROCESSSTATE_FINISHED;
return DE_TRUE;
}
static deBool deProcess_sendSignal (deProcess* process, int sigNum)
{
if (process->state != PROCESSSTATE_RUNNING)
{
deProcess_setError(process, "Process is not running");
return DE_FALSE;
}
if (kill(process->pid, sigNum) == 0)
return DE_TRUE;
else
{
deProcess_setErrorFromErrno(process, "kill() failed");
return DE_FALSE;
}
}
deBool deProcess_terminate (deProcess* process)
{
return deProcess_sendSignal(process, SIGTERM);
}
deBool deProcess_kill (deProcess* process)
{
return deProcess_sendSignal(process, SIGKILL);
}
deFile* deProcess_getStdIn (deProcess* process)
{
return process->standardIn;
}
deFile* deProcess_getStdOut (deProcess* process)
{
return process->standardOut;
}
deFile* deProcess_getStdErr (deProcess* process)
{
return process->standardErr;
}
deBool deProcess_closeStdIn (deProcess* process)
{
if (process->standardIn)
{
deFile_destroy(process->standardIn);
process->standardIn = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
deBool deProcess_closeStdOut (deProcess* process)
{
if (process->standardOut)
{
deFile_destroy(process->standardOut);
process->standardOut = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
deBool deProcess_closeStdErr (deProcess* process)
{
if (process->standardErr)
{
deFile_destroy(process->standardErr);
process->standardErr = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
#elif (DE_OS == DE_OS_WIN32)
#define VC_EXTRALEAN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <strsafe.h>
typedef enum ProcessState_e
{
PROCESSSTATE_NOT_STARTED = 0,
PROCESSSTATE_RUNNING,
PROCESSSTATE_FINISHED,
PROCESSSTATE_LAST
} ProcessState;
struct deProcess_s
{
ProcessState state;
char* lastError;
int exitCode;
PROCESS_INFORMATION procInfo;
deFile* standardIn;
deFile* standardOut;
deFile* standardErr;
};
static deBool deProcess_setError (deProcess* process, const char* error)
{
if (process->lastError)
{
deFree(process->lastError);
process->lastError = DE_NULL;
}
process->lastError = deStrdup(error);
return process->lastError != DE_NULL;
}
static deBool deProcess_setErrorFromWin32 (deProcess* process, const char* msg)
{
DWORD error = GetLastError();
LPSTR msgBuf;
char errBuf[256];
#if defined(UNICODE)
# error Unicode not supported.
#endif
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msgBuf, 0, DE_NULL) > 0)
{
deSprintf(errBuf, sizeof(errBuf), "%s, error %d: %s", msg, error, msgBuf);
LocalFree(msgBuf);
return deProcess_setError(process, errBuf);
}
else
{
/* Failed to get error str. */
deSprintf(errBuf, sizeof(errBuf), "%s, error %d", msg, error);
return deProcess_setError(process, errBuf);
}
}
deProcess* deProcess_create (void)
{
deProcess* process = (deProcess*)deCalloc(sizeof(deProcess));
if (!process)
return DE_NULL;
process->state = PROCESSSTATE_NOT_STARTED;
return process;
}
void deProcess_cleanupHandles (deProcess* process)
{
DE_ASSERT(!deProcess_isRunning(process));
if (process->standardErr)
deFile_destroy(process->standardErr);
if (process->standardOut)
deFile_destroy(process->standardOut);
if (process->standardIn)
deFile_destroy(process->standardIn);
if (process->procInfo.hProcess)
CloseHandle(process->procInfo.hProcess);
if (process->procInfo.hThread)
CloseHandle(process->procInfo.hThread);
process->standardErr = DE_NULL;
process->standardOut = DE_NULL;
process->standardIn = DE_NULL;
process->procInfo.hProcess = DE_NULL;
process->procInfo.hThread = DE_NULL;
}
void deProcess_destroy (deProcess* process)
{
if (deProcess_isRunning(process))
{
deProcess_kill(process);
deProcess_waitForFinish(process);
}
deProcess_cleanupHandles(process);
deFree(process->lastError);
deFree(process);
}
const char* deProcess_getLastError (const deProcess* process)
{
return process->lastError ? process->lastError : "No error";
}
int deProcess_getExitCode (const deProcess* process)
{
return process->exitCode;
}
deBool deProcess_start (deProcess* process, const char* commandLine, const char* workingDirectory)
{
SECURITY_ATTRIBUTES securityAttr;
STARTUPINFO startInfo;
/* Pipes. */
HANDLE stdInRead = DE_NULL;
HANDLE stdInWrite = DE_NULL;
HANDLE stdOutRead = DE_NULL;
HANDLE stdOutWrite = DE_NULL;
HANDLE stdErrRead = DE_NULL;
HANDLE stdErrWrite = DE_NULL;
if (process->state == PROCESSSTATE_RUNNING)
{
deProcess_setError(process, "Process already running");
return DE_FALSE;
}
else if (process->state == PROCESSSTATE_FINISHED)
{
/* Process finished, clean up old cruft. */
deProcess_cleanupHandles(process);
process->state = PROCESSSTATE_NOT_STARTED;
}
deMemset(&startInfo, 0, sizeof(startInfo));
deMemset(&securityAttr, 0, sizeof(securityAttr));
/* Security attributes for inheriting handle. */
securityAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
securityAttr.bInheritHandle = TRUE;
securityAttr.lpSecurityDescriptor = DE_NULL;
/* Create pipes. \todo [2011-10-03 pyry] Clean up handles on error! */
if (!CreatePipe(&stdInRead, &stdInWrite, &securityAttr, 0) ||
!SetHandleInformation(stdInWrite, HANDLE_FLAG_INHERIT, 0))
{
deProcess_setErrorFromWin32(process, "CreatePipe() failed");
CloseHandle(stdInRead);
CloseHandle(stdInWrite);
return DE_FALSE;
}
if (!CreatePipe(&stdOutRead, &stdOutWrite, &securityAttr, 0) ||
!SetHandleInformation(stdOutRead, HANDLE_FLAG_INHERIT, 0))
{
deProcess_setErrorFromWin32(process, "CreatePipe() failed");
CloseHandle(stdInRead);
CloseHandle(stdInWrite);
CloseHandle(stdOutRead);
CloseHandle(stdOutWrite);
return DE_FALSE;
}
if (!CreatePipe(&stdErrRead, &stdErrWrite, &securityAttr, 0) ||
!SetHandleInformation(stdErrRead, HANDLE_FLAG_INHERIT, 0))
{
deProcess_setErrorFromWin32(process, "CreatePipe() failed");
CloseHandle(stdInRead);
CloseHandle(stdInWrite);
CloseHandle(stdOutRead);
CloseHandle(stdOutWrite);
CloseHandle(stdErrRead);
CloseHandle(stdErrWrite);
return DE_FALSE;
}
/* Setup startup info. */
startInfo.cb = sizeof(startInfo);
startInfo.hStdError = stdErrWrite;
startInfo.hStdOutput = stdOutWrite;
startInfo.hStdInput = stdInRead;
startInfo.dwFlags |= STARTF_USESTDHANDLES;
if (!CreateProcess(DE_NULL, (LPTSTR)commandLine, DE_NULL, DE_NULL, TRUE /* inherit handles */, 0, DE_NULL, workingDirectory, &startInfo, &process->procInfo))
{
/* Store error info. */
deProcess_setErrorFromWin32(process, "CreateProcess() failed");
/* Close all handles. */
CloseHandle(stdInRead);
CloseHandle(stdInWrite);
CloseHandle(stdOutRead);
CloseHandle(stdOutWrite);
CloseHandle(stdErrRead);
CloseHandle(stdErrWrite);
return DE_FALSE;
}
process->state = PROCESSSTATE_RUNNING;
/* Close our ends of handles.*/
CloseHandle(stdErrWrite);
CloseHandle(stdOutWrite);
CloseHandle(stdInRead);
/* Construct stdio file objects \note May fail, not detected. */
process->standardIn = deFile_createFromHandle((deUintptr)stdInWrite);
process->standardOut = deFile_createFromHandle((deUintptr)stdOutRead);
process->standardErr = deFile_createFromHandle((deUintptr)stdErrRead);
return DE_TRUE;
}
deBool deProcess_isRunning (deProcess* process)
{
if (process->state == PROCESSSTATE_RUNNING)
{
int exitCode;
BOOL result = GetExitCodeProcess(process->procInfo.hProcess, (LPDWORD)&exitCode);
if (result != TRUE)
{
deProcess_setErrorFromWin32(process, "GetExitCodeProcess() failed");
return DE_FALSE;
}
if (exitCode == STILL_ACTIVE)
return DE_TRUE;
else
{
/* Done. */
process->exitCode = exitCode;
process->state = PROCESSSTATE_FINISHED;
return DE_FALSE;
}
}
else
return DE_FALSE;
}
deBool deProcess_waitForFinish (deProcess* process)
{
if (process->state == PROCESSSTATE_RUNNING)
{
if (WaitForSingleObject(process->procInfo.hProcess, INFINITE) != WAIT_OBJECT_0)
{
deProcess_setErrorFromWin32(process, "WaitForSingleObject() failed");
return DE_FALSE;
}
return !deProcess_isRunning(process);
}
else
{
deProcess_setError(process, "Process is not running");
return DE_FALSE;
}
}
static deBool stopProcess (deProcess* process, deBool kill)
{
if (process->state == PROCESSSTATE_RUNNING)
{
if (!TerminateProcess(process->procInfo.hProcess, kill ? -1 : 0))
{
deProcess_setErrorFromWin32(process, "TerminateProcess() failed");
return DE_FALSE;
}
else
return DE_TRUE;
}
else
{
deProcess_setError(process, "Process is not running");
return DE_FALSE;
}
}
deBool deProcess_terminate (deProcess* process)
{
return stopProcess(process, DE_FALSE);
}
deBool deProcess_kill (deProcess* process)
{
return stopProcess(process, DE_TRUE);
}
deFile* deProcess_getStdIn (deProcess* process)
{
return process->standardIn;
}
deFile* deProcess_getStdOut (deProcess* process)
{
return process->standardOut;
}
deFile* deProcess_getStdErr (deProcess* process)
{
return process->standardErr;
}
deBool deProcess_closeStdIn (deProcess* process)
{
if (process->standardIn)
{
deFile_destroy(process->standardIn);
process->standardIn = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
deBool deProcess_closeStdOut (deProcess* process)
{
if (process->standardOut)
{
deFile_destroy(process->standardOut);
process->standardOut = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
deBool deProcess_closeStdErr (deProcess* process)
{
if (process->standardErr)
{
deFile_destroy(process->standardErr);
process->standardErr = DE_NULL;
return DE_TRUE;
}
else
return DE_FALSE;
}
#else
# error Implement deProcess for your OS.
#endif