#include <stdio.h>
#include <git2.h>
#include "git2_util.h"
#include "process.h"
#include "strlist.h"
#include "fs_path.h"
#ifndef DWORD_MAX
# define DWORD_MAX INT32_MAX
#endif
#define ENV_MAX 32767
struct git_process {
char *app_name;
wchar_t *app_path;
wchar_t *cmdline;
wchar_t *env;
wchar_t *cwd;
unsigned int capture_in : 1,
capture_out : 1,
capture_err : 1;
PROCESS_INFORMATION process_info;
HANDLE child_in;
HANDLE child_out;
HANDLE child_err;
git_process_result_status status;
};
int git_process__cmdline(
git_str *out,
const char **in,
size_t in_len)
{
bool quoted = false;
const char *c;
size_t i;
for (i = 0; i < in_len; i++) {
if (i)
git_str_putc(out, ' ');
for (c = in[i]; *c; c++) {
if ((*c == ' ' || *c == '\t') && !quoted) {
git_str_putc(out, '"');
quoted = true;
} else if (*c != ' ' && *c != '\t' && quoted) {
git_str_putc(out, '"');
quoted = false;
}
if (*c == '"' || *c == '\\')
git_str_putc(out, '\\');
git_str_putc(out, *c);
}
}
return git_str_oom(out) ? -1 : 0;
}
GIT_INLINE(bool) is_delete_env(const char *env)
{
char *c = strchr(env, '=');
if (c == NULL)
return false;
return *(c+1) == '\0';
}
static int merge_env(wchar_t **out, const char **in, size_t in_len, bool exclude_env)
{
git_str merged = GIT_STR_INIT;
wchar_t *in16 = NULL, *env = NULL, *e;
char *e8 = NULL;
size_t e_len;
int ret = 0;
size_t i;
*out = NULL;
in16 = git__malloc(ENV_MAX * sizeof(wchar_t));
GIT_ERROR_CHECK_ALLOC(in16);
e8 = git__malloc(ENV_MAX);
GIT_ERROR_CHECK_ALLOC(e8);
for (i = 0; in && i < in_len; i++) {
if (is_delete_env(in[i]))
continue;
if ((ret = git_utf8_to_16(in16, ENV_MAX, in[i])) < 0)
goto done;
git_str_put(&merged, (const char *)in16, ret * 2);
git_str_put(&merged, "\0\0", 2);
}
if (!exclude_env) {
env = GetEnvironmentStringsW();
for (e = env; *e; e += (e_len + 1)) {
e_len = wcslen(e);
if ((ret = git_utf8_from_16(e8, ENV_MAX, e)) < 0)
goto done;
if (git_strlist_contains_key(in, in_len, e8, '='))
continue;
git_str_put(&merged, (const char *)e, e_len * 2);
git_str_put(&merged, "\0\0", 2);
}
}
git_str_put(&merged, "\0\0", 2);
*out = (wchar_t *)git_str_detach(&merged);
done:
if (env)
FreeEnvironmentStringsW(env);
git_str_dispose(&merged);
git__free(e8);
git__free(in16);
return ret < 0 ? -1 : 0;
}
static int process_new(
git_process **out,
const char *appname,
const char *cmdline,
const char **env,
size_t env_len,
git_process_options *opts)
{
git_process *process;
int error = 0;
*out = NULL;
process = git__calloc(1, sizeof(git_process));
GIT_ERROR_CHECK_ALLOC(process);
if (appname && (process->app_name = git__strdup(appname)) == NULL) {
error = -1;
goto done;
}
if (git_utf8_to_16_alloc(&process->cmdline, cmdline) < 0) {
error = -1;
goto done;
}
if (opts && opts->cwd &&
git_utf8_to_16_alloc(&process->cwd, opts->cwd) < 0) {
error = -1;
goto done;
}
if (env && (error = merge_env(&process->env, env, env_len, opts && opts->exclude_env) < 0))
goto done;
if (opts) {
process->capture_in = opts->capture_in;
process->capture_out = opts->capture_out;
process->capture_err = opts->capture_err;
}
done:
if (error)
git_process_free(process);
else
*out = process;
return error;
}
int git_process_new_from_cmdline(
git_process **out,
const char *cmdline,
const char **env,
size_t env_len,
git_process_options *opts)
{
GIT_ASSERT_ARG(out && cmdline);
return process_new(out, NULL, cmdline, env, env_len, opts);
}
int git_process_new(
git_process **out,
const char **args,
size_t args_len,
const char **env,
size_t env_len,
git_process_options *opts)
{
git_str cmd_path = GIT_STR_INIT, cmdline = GIT_STR_INIT;
int error;
GIT_ASSERT_ARG(out && args && args_len > 0);
if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0)
goto done;
error = process_new(out, args[0], cmdline.ptr, env, env_len, opts);
done:
git_str_dispose(&cmd_path);
git_str_dispose(&cmdline);
return error;
}
#define CLOSE_HANDLE(h) do { if ((h) != NULL) CloseHandle(h); } while(0)
int git_process_start(git_process *process)
{
STARTUPINFOW startup_info;
SECURITY_ATTRIBUTES security_attrs;
DWORD flags = CREATE_UNICODE_ENVIRONMENT;
HANDLE in[2] = { NULL, NULL },
out[2] = { NULL, NULL },
err[2] = { NULL, NULL };
if (process->app_name) {
git_str cmd_path = GIT_STR_INIT;
int error;
if ((error = git_fs_path_find_executable(&cmd_path, process->app_name)) == 0)
error = git_utf8_to_16_alloc(&process->app_path, cmd_path.ptr);
git_str_dispose(&cmd_path);
if (error < 0)
goto on_error;
}
memset(&security_attrs, 0, sizeof(SECURITY_ATTRIBUTES));
security_attrs.bInheritHandle = TRUE;
memset(&startup_info, 0, sizeof(STARTUPINFOW));
startup_info.cb = sizeof(STARTUPINFOW);
startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if (process->capture_in) {
if (!CreatePipe(&in[0], &in[1], &security_attrs, 0) ||
!SetHandleInformation(in[1], HANDLE_FLAG_INHERIT, 0)) {
git_error_set(GIT_ERROR_OS, "could not create pipe");
goto on_error;
}
startup_info.hStdInput = in[0];
startup_info.dwFlags |= STARTF_USESTDHANDLES;
}
if (process->capture_out) {
if (!CreatePipe(&out[0], &out[1], &security_attrs, 0) ||
!SetHandleInformation(out[0], HANDLE_FLAG_INHERIT, 0)) {
git_error_set(GIT_ERROR_OS, "could not create pipe");
goto on_error;
}
startup_info.hStdOutput = out[1];
startup_info.dwFlags |= STARTF_USESTDHANDLES;
}
if (process->capture_err) {
if (!CreatePipe(&err[0], &err[1], &security_attrs, 0) ||
!SetHandleInformation(err[0], HANDLE_FLAG_INHERIT, 0)) {
git_error_set(GIT_ERROR_OS, "could not create pipe");
goto on_error;
}
startup_info.hStdError = err[1];
startup_info.dwFlags |= STARTF_USESTDHANDLES;
}
memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
if (!CreateProcessW(process->app_path, process->cmdline,
NULL, NULL, TRUE, flags, process->env,
process->cwd,
&startup_info,
&process->process_info)) {
git_error_set(GIT_ERROR_OS, "could not create process");
goto on_error;
}
CLOSE_HANDLE(in[0]); process->child_in = in[1];
CLOSE_HANDLE(out[1]); process->child_out = out[0];
CLOSE_HANDLE(err[1]); process->child_err = err[0];
return 0;
on_error:
CLOSE_HANDLE(in[0]); CLOSE_HANDLE(in[1]);
CLOSE_HANDLE(out[0]); CLOSE_HANDLE(out[1]);
CLOSE_HANDLE(err[0]); CLOSE_HANDLE(err[1]);
return -1;
}
int git_process_id(p_pid_t *out, git_process *process)
{
GIT_ASSERT(out && process);
if (!process->process_info.dwProcessId) {
git_error_set(GIT_ERROR_INVALID, "process not running");
return -1;
}
*out = process->process_info.dwProcessId;
return 0;
}
ssize_t git_process_read(git_process *process, void *buf, size_t count)
{
DWORD ret;
if (count > DWORD_MAX)
count = DWORD_MAX;
if (count > SSIZE_MAX)
count = SSIZE_MAX;
if (!ReadFile(process->child_out, buf, (DWORD)count, &ret, NULL)) {
if (GetLastError() == ERROR_BROKEN_PIPE)
return 0;
git_error_set(GIT_ERROR_OS, "could not read");
return -1;
}
return ret;
}
ssize_t git_process_write(git_process *process, const void *buf, size_t count)
{
DWORD ret;
if (count > DWORD_MAX)
count = DWORD_MAX;
if (count > SSIZE_MAX)
count = SSIZE_MAX;
if (!WriteFile(process->child_in, buf, (DWORD)count, &ret, NULL)) {
git_error_set(GIT_ERROR_OS, "could not write");
return -1;
}
return ret;
}
int git_process_close_in(git_process *process)
{
if (!process->capture_in) {
git_error_set(GIT_ERROR_INVALID, "input is not open");
return -1;
}
if (process->child_in) {
CloseHandle(process->child_in);
process->child_in = NULL;
}
return 0;
}
int git_process_close_out(git_process *process)
{
if (!process->capture_out) {
git_error_set(GIT_ERROR_INVALID, "output is not open");
return -1;
}
if (process->child_out) {
CloseHandle(process->child_out);
process->child_out = NULL;
}
return 0;
}
int git_process_close_err(git_process *process)
{
if (!process->capture_err) {
git_error_set(GIT_ERROR_INVALID, "error is not open");
return -1;
}
if (process->child_err) {
CloseHandle(process->child_err);
process->child_err = NULL;
}
return 0;
}
int git_process_close(git_process *process)
{
if (process->child_in) {
CloseHandle(process->child_in);
process->child_in = NULL;
}
if (process->child_out) {
CloseHandle(process->child_out);
process->child_out = NULL;
}
if (process->child_err) {
CloseHandle(process->child_err);
process->child_err = NULL;
}
CloseHandle(process->process_info.hProcess);
process->process_info.hProcess = NULL;
CloseHandle(process->process_info.hThread);
process->process_info.hThread = NULL;
return 0;
}
int git_process_wait(git_process_result *result, git_process *process)
{
DWORD exitcode;
if (result)
memset(result, 0, sizeof(git_process_result));
if (!process->process_info.dwProcessId) {
git_error_set(GIT_ERROR_INVALID, "process is stopped");
return -1;
}
if (WaitForSingleObject(process->process_info.hProcess, INFINITE) == WAIT_FAILED) {
git_error_set(GIT_ERROR_OS, "could not wait for process");
return -1;
}
if (!GetExitCodeProcess(process->process_info.hProcess, &exitcode)) {
git_error_set(GIT_ERROR_OS, "could not get process exit code");
return -1;
}
result->status = GIT_PROCESS_STATUS_NORMAL;
result->exitcode = exitcode;
memset(&process->process_info, 0, sizeof(PROCESS_INFORMATION));
return 0;
}
int git_process_result_msg(git_str *out, git_process_result *result)
{
if (result->status == GIT_PROCESS_STATUS_NONE) {
return git_str_puts(out, "process not started");
} else if (result->status == GIT_PROCESS_STATUS_NORMAL) {
return git_str_printf(out, "process exited with code %d",
result->exitcode);
} else if (result->signal) {
return git_str_printf(out, "process exited on signal %d",
result->signal);
}
return git_str_puts(out, "unknown error");
}
void git_process_free(git_process *process)
{
if (!process)
return;
if (process->process_info.hProcess)
git_process_close(process);
git__free(process->env);
git__free(process->cwd);
git__free(process->cmdline);
git__free(process->app_path);
git__free(process->app_name);
git__free(process);
}