/*
The latest version of this library is available on GitHub;
https://github.com/sheredom/process.h
*/
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#ifndef SHEREDOM_PROCESS_H_INCLUDED
#define SHEREDOM_PROCESS_H_INCLUDED
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(_MSC_VER)
#pragma warning(push, 1)
#endif
#include <stdio.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if !defined(_MSC_VER)
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#if defined(_MSC_VER)
#ifdef _WIN64
typedef __int64 intptr_t;
typedef unsigned __int64 size_t;
#else
typedef int intptr_t;
typedef unsigned int size_t;
#endif
typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
#pragma warning(push, 1)
struct process_process_information_s {
void *hProcess;
void *hThread;
unsigned long dwProcessId;
unsigned long dwThreadId;
};
struct process_security_attributes_s {
unsigned long nLength;
void *lpSecurityDescriptor;
int bInheritHandle;
};
struct process_startup_info_s {
unsigned long cb;
char *lpReserved;
char *lpDesktop;
char *lpTitle;
unsigned long dwX;
unsigned long dwY;
unsigned long dwXSize;
unsigned long dwYSize;
unsigned long dwXCountChars;
unsigned long dwYCountChars;
unsigned long dwFillAttribute;
unsigned long dwFlags;
unsigned short wShowWindow;
unsigned short cbReserved2;
unsigned char *lpReserved2;
void *hStdInput;
void *hStdOutput;
void *hStdError;
};
#pragma warning(pop)
__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
unsigned long);
__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
LPSECURITY_ATTRIBUTES,
unsigned long);
__declspec(dllimport) int __stdcall CreateProcessA(
const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
__declspec(dllimport) int __stdcall CloseHandle(void *);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
void *, unsigned long);
__declspec(dllimport) int __stdcall GetExitCodeProcess(
void *, unsigned long *lpExitCode);
__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int);
void *__cdecl _alloca(size_t);
#endif
#if defined(__clang__) || defined(__GNUC__)
#define process_pure __attribute__((pure))
#define process_weak __attribute__((weak))
#elif defined(_MSC_VER)
#define process_pure
#define process_weak __inline
#else
#error Non clang, non gcc, non MSVC compiler found!
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif
struct process_s {
FILE *stdin_file;
FILE *stdout_file;
FILE *stderr_file;
#if defined(_MSC_VER)
void *hProcess;
void *hStdInput;
void *hStdOutput;
void *hStdError;
#else
pid_t child;
#endif
};
#ifdef __clang__
#pragma clang diagnostic pop
#endif
enum process_option_e {
// stdout and stderr are the same FILE.
process_option_combined_stdout_stderr = 0x1,
// The child process should inherit the environment variables of the parent.
process_option_inherit_environment = 0x2
};
/// @brief Create a process.
/// @param command_line An array of strings for the command line to execute for
/// this process. The last element must be NULL to signify the end of the array.
/// @param options A bit field of process_option_e's to pass.
/// @param out_process The newly created process.
/// @return On success 0 is returned.
process_weak int process_create(const char *const command_line[], int options,
struct process_s *const out_process);
/// @brief Get the standard input file for a process.
/// @param process The process to query.
/// @return The file for standard input of the process.
///
/// The file returned can be written to by the parent process to feed data to
/// the standard input of the process.
process_pure process_weak FILE *
process_stdin(const struct process_s *const process);
/// @brief Get the standard output file for a process.
/// @param process The process to query.
/// @return The file for standard output of the process.
///
/// The file returned can be read from by the parent process to read data from
/// the standard output of the child process.
process_pure process_weak FILE *
process_stdout(const struct process_s *const process);
/// @brief Get the standard error file for a process.
/// @param process The process to query.
/// @return The file for standard error of the process.
///
/// The file returned can be read from by the parent process to read data from
/// the standard error of the child process.
///
/// If the process was created with the process_option_combined_stdout_stderr
/// option bit set, this function will return NULL, and the process_stdout
/// function should be used for both the standard output and error combined.
process_pure process_weak FILE *
process_stderr(const struct process_s *const process);
/// @brief Wait for a process to finish execution.
/// @param process The process to wait for.
/// @param out_return_code The return code of the returned process (can be
/// NULL).
/// @return On success 0 is returned.
process_weak int process_join(struct process_s *const process,
int *const out_return_code);
/// @brief Destroy a previously created process.
/// @param process The process to destroy.
///
/// If the process to be destroyed had not finished execution, it may out live
/// the parent process.
process_weak int process_destroy(struct process_s *const process);
int process_create(const char *const commandLine[], int options,
struct process_s *const out_process) {
#if defined(_MSC_VER)
int fd;
void *rd, *wr;
char *commandLineCombined;
size_t len;
int i, j;
const unsigned long startFUseStdHandles = 0x00000100;
const unsigned long handleFlagInherit = 0x00000001;
struct process_process_information_s processInfo;
struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
char *environment = 0;
struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0};
startInfo.cb = sizeof(startInfo);
startInfo.dwFlags = startFUseStdHandles;
if (process_option_inherit_environment !=
(options & process_option_inherit_environment)) {
environment = "\0\0";
}
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
return -1;
}
if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
return -1;
}
fd = _open_osfhandle((intptr_t)wr, 0);
if (-1 != fd) {
out_process->stdin_file = _fdopen(fd, "wb");
if (0 == out_process->stdin_file) {
return -1;
}
}
startInfo.hStdInput = rd;
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
return -1;
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
return -1;
}
fd = _open_osfhandle((intptr_t)rd, 0);
if (-1 != fd) {
out_process->stdout_file = _fdopen(fd, "rb");
if (0 == out_process->stdout_file) {
return -1;
}
}
startInfo.hStdOutput = wr;
if (process_option_combined_stdout_stderr ==
(options & process_option_combined_stdout_stderr)) {
out_process->stderr_file = out_process->stdout_file;
startInfo.hStdError = startInfo.hStdOutput;
} else {
if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
return -1;
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
return -1;
}
fd = _open_osfhandle((intptr_t)rd, 0);
if (-1 != fd) {
out_process->stderr_file = _fdopen(fd, "rb");
if (0 == out_process->stderr_file) {
return -1;
}
}
startInfo.hStdError = wr;
}
// Combine commandLine together into a single string
len = 0;
for (i = 0; commandLine[i]; i++) {
// For the ' ' between items and trailing '\0'
len++;
for (j = 0; '\0' != commandLine[i][j]; j++) {
len++;
}
}
commandLineCombined = (char *)_alloca(len);
if (!commandLineCombined) {
return -1;
}
// Gonna re-use len to store the write index into commandLineCombined
len = 0;
for (i = 0; commandLine[i]; i++) {
if (0 != i) {
commandLineCombined[len++] = ' ';
}
for (j = 0; '\0' != commandLine[i][j]; j++) {
commandLineCombined[len++] = commandLine[i][j];
}
}
commandLineCombined[len] = '\0';
if (!CreateProcessA(NULL,
commandLineCombined, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
1, // handles are inherited
0, // creation flags
environment, // use parent's environment
NULL, // use parent's current directory
(LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer
(LPPROCESS_INFORMATION)&processInfo)) {
return -1;
}
out_process->hProcess = processInfo.hProcess;
out_process->hStdInput = startInfo.hStdInput;
out_process->hStdOutput = startInfo.hStdOutput;
out_process->hStdError = startInfo.hStdError;
// We don't need the handle of the primary thread in the called process.
CloseHandle(processInfo.hThread);
return 0;
#else
int stdinfd[2];
int stdoutfd[2];
int stderrfd[2];
pid_t child;
if (0 != pipe(stdinfd)) {
return -1;
}
if (0 != pipe(stdoutfd)) {
return -1;
}
if (process_option_combined_stdout_stderr !=
(options & process_option_combined_stdout_stderr)) {
if (0 != pipe(stderrfd)) {
return -1;
}
}
child = fork();
if (-1 == child) {
return -1;
}
if (0 == child) {
// Close the stdin write end
close(stdinfd[1]);
// Map the read end to stdin
dup2(stdinfd[0], STDIN_FILENO);
// Close the stdout read end
close(stdoutfd[0]);
// Map the write end to stdout
dup2(stdoutfd[1], STDOUT_FILENO);
if (process_option_combined_stdout_stderr ==
(options & process_option_combined_stdout_stderr)) {
dup2(STDOUT_FILENO, STDERR_FILENO);
} else {
// Close the stderr read end
close(stderrfd[0]);
// Map the write end to stdout
dup2(stderrfd[1], STDERR_FILENO);
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wold-style-cast"
#endif
if (process_option_inherit_environment !=
(options & process_option_inherit_environment)) {
char *const environment[1] = {0};
exit(execve(commandLine[0], (char *const *)commandLine, environment));
} else {
exit(execvp(commandLine[0], (char *const *)commandLine));
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
} else {
// Close the stdin read end
close(stdinfd[0]);
// Store the stdin write end
out_process->stdin_file = fdopen(stdinfd[1], "wb");
// Close the stdout write end
close(stdoutfd[1]);
// Store the stdout read end
out_process->stdout_file = fdopen(stdoutfd[0], "rb");
if (process_option_combined_stdout_stderr ==
(options & process_option_combined_stdout_stderr)) {
out_process->stderr_file = out_process->stdout_file;
} else {
// Close the stderr write end
close(stderrfd[1]);
// Store the stderr read end
out_process->stderr_file = fdopen(stderrfd[0], "rb");
}
// Store the child's pid
out_process->child = child;
return 0;
}
#endif
}
FILE *process_stdin(const struct process_s *const process) {
return process->stdin_file;
}
FILE *process_stdout(const struct process_s *const process) {
return process->stdout_file;
}
FILE *process_stderr(const struct process_s *const process) {
if (process->stdout_file != process->stderr_file) {
return process->stderr_file;
} else {
return 0;
}
}
int process_join(struct process_s *const process, int *const out_return_code) {
#if defined(_MSC_VER)
const unsigned long infinite = 0xFFFFFFFF;
if (0 != process->stdin_file) {
fclose(process->stdin_file);
process->stdin_file = 0;
}
if (0 != process->hStdInput) {
CloseHandle(process->hStdInput);
process->hStdInput = NULL;
}
WaitForSingleObject(process->hProcess, infinite);
if (0 != process->hStdOutput) {
CloseHandle(process->hStdOutput);
if (process->hStdError == process->hStdOutput) {
process->hStdError = NULL;
}
process->hStdOutput = NULL;
}
if (0 != process->hStdError) {
CloseHandle(process->hStdError);
process->hStdError = NULL;
}
if (out_return_code) {
if (!GetExitCodeProcess(process->hProcess,
(unsigned long *)out_return_code)) {
return -1;
}
}
return 0;
#else
int status;
if (0 != process->stdin_file) {
fclose(process->stdin_file);
process->stdin_file = 0;
}
if (process->child != waitpid(process->child, &status, 0)) {
return -1;
}
if (out_return_code) {
if (WIFEXITED(status)) {
*out_return_code = WEXITSTATUS(status);
} else {
*out_return_code = 0;
}
}
return 0;
#endif
}
int process_destroy(struct process_s *const process) {
if (0 != process->stdin_file) {
fclose(process->stdin_file);
}
fclose(process->stdout_file);
if (process->stdout_file != process->stderr_file) {
fclose(process->stderr_file);
}
#if defined(_MSC_VER)
CloseHandle(process->hProcess);
if (0 != process->hStdInput) {
CloseHandle(process->hStdInput);
process->hStdInput = NULL;
}
if (0 != process->hStdOutput) {
CloseHandle(process->hStdOutput);
if (process->hStdError == process->hStdOutput) {
process->hStdError = NULL;
}
process->hStdOutput = NULL;
}
if (0 != process->hStdError) {
CloseHandle(process->hStdError);
process->hStdError = NULL;
}
#endif
return 0;
}
#if defined(__cplusplus)
} // extern "C"
#endif
#endif /* SHEREDOM_PROCESS_H_INCLUDED */