mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
Introduce git_process class that invokes processes
This commit is contained in:
156
src/util/process.h
Normal file
156
src/util/process.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_process_h__
|
||||
#define INCLUDE_process_h__
|
||||
|
||||
typedef struct git_process git_process;
|
||||
|
||||
typedef struct {
|
||||
int capture_in : 1,
|
||||
capture_out : 1,
|
||||
capture_err : 1,
|
||||
exclude_env : 1;
|
||||
|
||||
char *cwd;
|
||||
} git_process_options;
|
||||
|
||||
typedef enum {
|
||||
GIT_PROCESS_STATUS_NONE,
|
||||
GIT_PROCESS_STATUS_NORMAL,
|
||||
GIT_PROCESS_STATUS_ERROR
|
||||
} git_process_result_status;
|
||||
|
||||
#define GIT_PROCESS_RESULT_INIT { GIT_PROCESS_STATUS_NONE }
|
||||
|
||||
typedef struct {
|
||||
git_process_result_status status;
|
||||
int exitcode;
|
||||
int signal;
|
||||
} git_process_result;
|
||||
|
||||
#define GIT_PROCESS_OPTIONS_INIT { 0 }
|
||||
|
||||
/**
|
||||
* Create a new process. The command to run should be specified as the
|
||||
* element of the `arg` array.
|
||||
*
|
||||
* This function will add the given environment variables (in `env`)
|
||||
* to the current environment. Operations on environment variables
|
||||
* are not thread safe, so you may not modify the environment during
|
||||
* this call. You can avoid this by setting `exclude_env` in the
|
||||
* options and providing the entire environment yourself.
|
||||
*
|
||||
* @param out location to store the process
|
||||
* @param args the command (with arguments) to run
|
||||
* @param args_len the length of the args array
|
||||
* @param env environment variables to add (or NULL)
|
||||
* @param env_len the length of the env len
|
||||
* @param opts the options for creating the process
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
extern 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);
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
|
||||
/* Windows path parsing is tricky; this helper function is for testing. */
|
||||
extern int git_process__cmdline(
|
||||
git_str *out,
|
||||
const char **in,
|
||||
size_t in_len);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Start the process.
|
||||
*
|
||||
* @param process the process to start
|
||||
* @return 0 or an error code
|
||||
*/
|
||||
extern int git_process_start(git_process *process);
|
||||
|
||||
/**
|
||||
* Read from the process's stdout. The process must have been created with
|
||||
* `capture_out` set to true.
|
||||
*
|
||||
* @param process the process to read from
|
||||
* @param buf the buf to read into
|
||||
* @param count maximum number of bytes to read
|
||||
* @return number of bytes read or an error code
|
||||
*/
|
||||
extern ssize_t git_process_read(git_process *process, void *buf, size_t count);
|
||||
|
||||
/**
|
||||
* Write to the process's stdin. The process must have been created with
|
||||
* `capture_in` set to true.
|
||||
*
|
||||
* @param process the process to write to
|
||||
* @param buf the buf to write
|
||||
* @param count maximum number of bytes to write
|
||||
* @return number of bytes written or an error code
|
||||
*/
|
||||
extern ssize_t git_process_write(git_process *process, const void *buf, size_t count);
|
||||
|
||||
/**
|
||||
* Wait for the process to finish.
|
||||
*
|
||||
* @param result the result of the process or NULL
|
||||
* @param process the process to wait on
|
||||
*/
|
||||
extern int git_process_wait(git_process_result *result, git_process *process);
|
||||
|
||||
/**
|
||||
* Close the input pipe from the child.
|
||||
*
|
||||
* @param process the process to close the pipe on
|
||||
*/
|
||||
extern int git_process_close_in(git_process *process);
|
||||
|
||||
/**
|
||||
* Close the output pipe from the child.
|
||||
*
|
||||
* @param process the process to close the pipe on
|
||||
*/
|
||||
extern int git_process_close_out(git_process *process);
|
||||
|
||||
/**
|
||||
* Close the error pipe from the child.
|
||||
*
|
||||
* @param process the process to close the pipe on
|
||||
*/
|
||||
extern int git_process_close_err(git_process *process);
|
||||
|
||||
/**
|
||||
* Close all resources that are used by the process. This does not
|
||||
* wait for the process to complete.
|
||||
*
|
||||
* @parma process the process to close
|
||||
*/
|
||||
extern int git_process_close(git_process *process);
|
||||
|
||||
/**
|
||||
* Place a human-readable error message in the given git buffer.
|
||||
*
|
||||
* @param msg the buffer to store the message
|
||||
* @param result the process result that produced an error
|
||||
*/
|
||||
extern int git_process_result_msg(git_str *msg, git_process_result *result);
|
||||
|
||||
/**
|
||||
* Free a process structure
|
||||
*
|
||||
* @param process the process to free
|
||||
*/
|
||||
extern void git_process_free(git_process *process);
|
||||
|
||||
#endif
|
||||
499
src/util/unix/process.c
Normal file
499
src/util/unix/process.c
Normal file
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <git2.h>
|
||||
|
||||
#include "git2_util.h"
|
||||
#include "vector.h"
|
||||
#include "process.h"
|
||||
#include "strlist.h"
|
||||
|
||||
extern char **environ;
|
||||
|
||||
struct git_process {
|
||||
char **args;
|
||||
char **env;
|
||||
|
||||
char *cwd;
|
||||
|
||||
unsigned int capture_in : 1,
|
||||
capture_out : 1,
|
||||
capture_err : 1;
|
||||
|
||||
pid_t pid;
|
||||
|
||||
int child_in;
|
||||
int child_out;
|
||||
int child_err;
|
||||
git_process_result_status status;
|
||||
};
|
||||
|
||||
GIT_INLINE(bool) is_delete_env(const char *env)
|
||||
{
|
||||
char *c = index(env, '=');
|
||||
|
||||
if (c == NULL)
|
||||
return false;
|
||||
|
||||
return *(c+1) == '\0';
|
||||
}
|
||||
|
||||
static int merge_env(
|
||||
char ***out,
|
||||
const char **env,
|
||||
size_t env_len,
|
||||
bool exclude_env)
|
||||
{
|
||||
git_vector merged = GIT_VECTOR_INIT;
|
||||
char **kv, *dup;
|
||||
size_t max, cnt;
|
||||
int error = 0;
|
||||
|
||||
for (max = env_len, kv = environ; !exclude_env && *kv; kv++)
|
||||
max++;
|
||||
|
||||
if ((error = git_vector_init(&merged, max, NULL)) < 0)
|
||||
goto on_error;
|
||||
|
||||
for (cnt = 0; env && cnt < env_len; cnt++) {
|
||||
if (is_delete_env(env[cnt]))
|
||||
continue;
|
||||
|
||||
dup = git__strdup(env[cnt]);
|
||||
GIT_ERROR_CHECK_ALLOC(dup);
|
||||
|
||||
if ((error = git_vector_insert(&merged, dup)) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (!exclude_env) {
|
||||
for (kv = environ; *kv; kv++) {
|
||||
if (env && git_strlist_contains_key(env, env_len, *kv, '='))
|
||||
continue;
|
||||
|
||||
dup = git__strdup(*kv);
|
||||
GIT_ERROR_CHECK_ALLOC(dup);
|
||||
|
||||
if ((error = git_vector_insert(&merged, dup)) < 0)
|
||||
goto on_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (merged.length == 0) {
|
||||
*out = NULL;
|
||||
error = 0;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
git_vector_insert(&merged, NULL);
|
||||
|
||||
*out = (char **)merged.contents;
|
||||
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_vector_free_deep(&merged);
|
||||
return error;
|
||||
}
|
||||
|
||||
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_process *process;
|
||||
|
||||
GIT_ASSERT_ARG(out && args && args_len > 0);
|
||||
|
||||
*out = NULL;
|
||||
|
||||
process = git__calloc(sizeof(git_process), 1);
|
||||
GIT_ERROR_CHECK_ALLOC(process);
|
||||
|
||||
if (git_strlist_copy_with_null(&process->args, args, args_len) < 0 ||
|
||||
merge_env(&process->env, env, env_len, opts->exclude_env) < 0) {
|
||||
git_process_free(process);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
process->capture_in = opts->capture_in;
|
||||
process->capture_out = opts->capture_out;
|
||||
process->capture_err = opts->capture_err;
|
||||
|
||||
if (opts->cwd) {
|
||||
process->cwd = git__strdup(opts->cwd);
|
||||
GIT_ERROR_CHECK_ALLOC(process->cwd);
|
||||
}
|
||||
}
|
||||
|
||||
process->child_in = -1;
|
||||
process->child_out = -1;
|
||||
process->child_err = -1;
|
||||
process->status = -1;
|
||||
|
||||
*out = process;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CLOSE_FD(fd) \
|
||||
if (fd >= 0) { \
|
||||
close(fd); \
|
||||
fd = -1; \
|
||||
}
|
||||
|
||||
static int try_read(size_t *out, int fd, void *buf, size_t len)
|
||||
{
|
||||
size_t read_len = 0;
|
||||
int ret = -1;
|
||||
|
||||
while (ret && read_len < len) {
|
||||
ret = read(fd, buf + read_len, len - read_len);
|
||||
|
||||
if (ret < 0 && errno != EAGAIN && errno != EINTR) {
|
||||
git_error_set(GIT_ERROR_OS, "could not read child status");
|
||||
return -1;
|
||||
}
|
||||
|
||||
read_len += ret;
|
||||
}
|
||||
|
||||
*out = read_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int read_status(int fd)
|
||||
{
|
||||
size_t status_len = sizeof(int) * 3, read_len = 0;
|
||||
char buffer[status_len], fn[128];
|
||||
int error, fn_error, os_error, fn_len = 0;
|
||||
|
||||
if ((error = try_read(&read_len, fd, buffer, status_len)) < 0)
|
||||
return error;
|
||||
|
||||
/* Immediate EOF indicates the exec succeeded. */
|
||||
if (read_len == 0)
|
||||
return 0;
|
||||
|
||||
if (read_len < status_len) {
|
||||
git_error_set(GIT_ERROR_INVALID, "child status truncated");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(&fn_error, &buffer[0], sizeof(int));
|
||||
memcpy(&os_error, &buffer[sizeof(int)], sizeof(int));
|
||||
memcpy(&fn_len, &buffer[sizeof(int) * 2], sizeof(int));
|
||||
|
||||
if (fn_len > 0) {
|
||||
fn_len = min(fn_len, (int)(ARRAY_SIZE(fn) - 1));
|
||||
|
||||
if ((error = try_read(&read_len, fd, fn, fn_len)) < 0)
|
||||
return error;
|
||||
|
||||
fn[fn_len] = '\0';
|
||||
} else {
|
||||
fn[0] = '\0';
|
||||
}
|
||||
|
||||
if (fn_error) {
|
||||
errno = os_error;
|
||||
git_error_set(GIT_ERROR_OS, "could not %s", fn[0] ? fn : "(unknown)");
|
||||
}
|
||||
|
||||
return fn_error;
|
||||
}
|
||||
|
||||
static bool try_write(int fd, const void *buf, size_t len)
|
||||
{
|
||||
size_t write_len;
|
||||
int ret;
|
||||
|
||||
for (write_len = 0; write_len < len; ) {
|
||||
ret = write(fd, buf + write_len, len - write_len);
|
||||
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
write_len += ret;
|
||||
}
|
||||
|
||||
return (len == write_len);
|
||||
}
|
||||
|
||||
static void write_status(int fd, const char *fn, int error, int os_error)
|
||||
{
|
||||
size_t status_len = sizeof(int) * 3, fn_len;
|
||||
char buffer[status_len];
|
||||
|
||||
fn_len = strlen(fn);
|
||||
|
||||
if (fn_len > INT_MAX)
|
||||
fn_len = INT_MAX;
|
||||
|
||||
memcpy(&buffer[0], &error, sizeof(int));
|
||||
memcpy(&buffer[sizeof(int)], &os_error, sizeof(int));
|
||||
memcpy(&buffer[sizeof(int) * 2], &fn_len, sizeof(int));
|
||||
|
||||
/* Do our best effort to write all the status. */
|
||||
if (!try_write(fd, buffer, status_len))
|
||||
return;
|
||||
|
||||
if (fn_len)
|
||||
try_write(fd, fn, fn_len);
|
||||
}
|
||||
|
||||
int git_process_start(git_process *process)
|
||||
{
|
||||
int in[2] = { -1, -1 }, out[2] = { -1, -1 },
|
||||
err[2] = { -1, -1 }, status[2] = { -1, -1 };
|
||||
int fdflags, state, error;
|
||||
pid_t pid;
|
||||
|
||||
/* Set up the pipes to read from/write to the process */
|
||||
if ((process->capture_in && pipe(in) < 0) ||
|
||||
(process->capture_out && pipe(out) < 0) ||
|
||||
(process->capture_err && pipe(err) < 0)) {
|
||||
git_error_set(GIT_ERROR_OS, "could not create pipe");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
/* Set up a self-pipe for status from the forked process. */
|
||||
if (pipe(status) < 0 ||
|
||||
(fdflags = fcntl(status[1], F_GETFD)) < 0 ||
|
||||
fcntl(status[1], F_SETFD, fdflags | FD_CLOEXEC) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not create pipe");
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
switch (pid = fork()) {
|
||||
case -1:
|
||||
git_error_set(GIT_ERROR_OS, "could not fork");
|
||||
goto on_error;
|
||||
|
||||
/* Child: start the process. */
|
||||
case 0:
|
||||
/* Close the opposing side of the pipes */
|
||||
CLOSE_FD(status[0]);
|
||||
|
||||
if (process->capture_in) {
|
||||
CLOSE_FD(in[1]);
|
||||
dup2(in[0], STDIN_FILENO);
|
||||
}
|
||||
|
||||
if (process->capture_out) {
|
||||
CLOSE_FD(out[0]);
|
||||
dup2(out[1], STDOUT_FILENO);
|
||||
}
|
||||
|
||||
if (process->capture_err) {
|
||||
CLOSE_FD(err[0]);
|
||||
dup2(err[1], STDERR_FILENO);
|
||||
}
|
||||
|
||||
if (process->cwd && (error = chdir(process->cwd)) < 0) {
|
||||
write_status(status[1], "chdir", error, errno);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Exec the process and write the results back if the
|
||||
* call fails. If it succeeds, we'll close the status
|
||||
* pipe (via CLOEXEC) and the parent will know.
|
||||
*/
|
||||
error = execve(process->args[0],
|
||||
process->args,
|
||||
process->env);
|
||||
|
||||
write_status(status[1], "execve", error, errno);
|
||||
exit(0);
|
||||
|
||||
/* Parent: make sure the child process exec'd correctly. */
|
||||
default:
|
||||
/* Close the opposing side of the pipes */
|
||||
CLOSE_FD(status[1]);
|
||||
|
||||
if (process->capture_in) {
|
||||
CLOSE_FD(in[0]);
|
||||
process->child_in = in[1];
|
||||
}
|
||||
|
||||
if (process->capture_out) {
|
||||
CLOSE_FD(out[1]);
|
||||
process->child_out = out[0];
|
||||
}
|
||||
|
||||
if (process->capture_err) {
|
||||
CLOSE_FD(err[1]);
|
||||
process->child_err = err[0];
|
||||
}
|
||||
|
||||
/* Try to read the status */
|
||||
process->status = status[0];
|
||||
if ((error = read_status(status[0])) < 0) {
|
||||
waitpid(process->pid, &state, 0);
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
process->pid = pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
on_error:
|
||||
CLOSE_FD(in[0]); CLOSE_FD(in[1]);
|
||||
CLOSE_FD(out[0]); CLOSE_FD(out[1]);
|
||||
CLOSE_FD(err[0]); CLOSE_FD(err[1]);
|
||||
CLOSE_FD(status[0]); CLOSE_FD(status[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t git_process_read(git_process *process, void *buf, size_t count)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
GIT_ASSERT_ARG(process);
|
||||
GIT_ASSERT(process->capture_out);
|
||||
|
||||
if (count > SSIZE_MAX)
|
||||
count = SSIZE_MAX;
|
||||
|
||||
if ((ret = read(process->child_out, buf, count)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not read from child process");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t git_process_write(git_process *process, const void *buf, size_t count)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
GIT_ASSERT_ARG(process);
|
||||
GIT_ASSERT(process->capture_in);
|
||||
|
||||
if (count > SSIZE_MAX)
|
||||
count = SSIZE_MAX;
|
||||
|
||||
if ((ret = write(process->child_in, buf, count)) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not write to child process");
|
||||
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;
|
||||
}
|
||||
|
||||
CLOSE_FD(process->child_in);
|
||||
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;
|
||||
}
|
||||
|
||||
CLOSE_FD(process->child_out);
|
||||
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;
|
||||
}
|
||||
|
||||
CLOSE_FD(process->child_err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_process_close(git_process *process)
|
||||
{
|
||||
CLOSE_FD(process->child_in);
|
||||
CLOSE_FD(process->child_out);
|
||||
CLOSE_FD(process->child_err);
|
||||
CLOSE_FD(process->status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_process_wait(git_process_result *result, git_process *process)
|
||||
{
|
||||
int state;
|
||||
|
||||
if (result)
|
||||
memset(result, 0, sizeof(git_process_result));
|
||||
|
||||
if (!process->pid) {
|
||||
git_error_set(GIT_ERROR_INVALID, "process is stopped");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (waitpid(process->pid, &state, 0) < 0) {
|
||||
git_error_set(GIT_ERROR_OS, "could not wait for child");
|
||||
return -1;
|
||||
}
|
||||
|
||||
process->pid = 0;
|
||||
|
||||
if (result) {
|
||||
if (WIFEXITED(state)) {
|
||||
result->status = GIT_PROCESS_STATUS_NORMAL;
|
||||
result->exitcode = WEXITSTATUS(state);
|
||||
} else if (WIFSIGNALED(state)) {
|
||||
result->status = GIT_PROCESS_STATUS_ERROR;
|
||||
result->signal = WTERMSIG(state);
|
||||
} else {
|
||||
result->status = GIT_PROCESS_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
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->pid)
|
||||
git_process_close(process);
|
||||
|
||||
git__free(process->cwd);
|
||||
git_strlist_free_with_null(process->args);
|
||||
git_strlist_free_with_null(process->env);
|
||||
git__free(process);
|
||||
}
|
||||
460
src/util/win32/process.c
Normal file
460
src/util/win32/process.c
Normal file
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright (C) the libgit2 contributors. All rights reserved.
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <git2.h>
|
||||
|
||||
#include "git2_util.h"
|
||||
#include "process.h"
|
||||
#include "strlist.h"
|
||||
|
||||
#ifndef DWORD_MAX
|
||||
# define DWORD_MAX INT32_MAX
|
||||
#endif
|
||||
|
||||
#define ENV_MAX 32767
|
||||
|
||||
struct git_process {
|
||||
wchar_t *appname;
|
||||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
* Windows processes have a single command-line that is split by the
|
||||
* invoked application into arguments (instead of an array of
|
||||
* command-line arguments). This command-line is split by space or
|
||||
* tab delimiters, unless that whitespace is within a double quote.
|
||||
* Literal double-quotes themselves can be escaped by a backslash,
|
||||
* but only when not within double quotes. Literal backslashes can
|
||||
* be escaped by a backslash.
|
||||
*
|
||||
* Effectively, this means that instead of thinking about quoting
|
||||
* individual strings, think about double quotes as an escaping
|
||||
* mechanism for whitespace.
|
||||
*
|
||||
* In other words (using ` as a string boundary):
|
||||
* [ `foo`, `bar` ] => `foo bar`
|
||||
* [ `foo bar` ] => `foo" "bar`
|
||||
* [ `foo bar`, `foo bar` ] => `foo" "bar foo" "bar`
|
||||
* [ `foo "bar" foo` ] => `foo" "\"bar\"" "foo`
|
||||
*/
|
||||
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++) {
|
||||
/* Arguments are delimited by an unquoted space */
|
||||
if (i)
|
||||
git_str_putc(out, ' ');
|
||||
|
||||
for (c = in[i]; *c; c++) {
|
||||
/* Start or stop quoting spaces within an argument */
|
||||
if ((*c == ' ' || *c == '\t') && !quoted) {
|
||||
git_str_putc(out, '"');
|
||||
quoted = true;
|
||||
} else if (*c != ' ' && *c != '\t' && quoted) {
|
||||
git_str_putc(out, '"');
|
||||
quoted = false;
|
||||
}
|
||||
|
||||
/* Escape double-quotes and backslashes */
|
||||
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;
|
||||
}
|
||||
|
||||
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_process *process;
|
||||
git_str cmdline = GIT_STR_INIT;
|
||||
int error;
|
||||
|
||||
GIT_ASSERT_ARG(out && args && args_len > 0);
|
||||
|
||||
*out = NULL;
|
||||
|
||||
process = git__calloc(1, sizeof(git_process));
|
||||
GIT_ERROR_CHECK_ALLOC(process);
|
||||
|
||||
if ((error = git_process__cmdline(&cmdline, args, args_len)) < 0)
|
||||
goto done;
|
||||
|
||||
if (git_utf8_to_16_alloc(&process->appname, args[0]) < 0 ||
|
||||
git_utf8_to_16_alloc(&process->cmdline, cmdline.ptr) < 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;
|
||||
|
||||
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 };
|
||||
|
||||
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->appname, 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;
|
||||
}
|
||||
|
||||
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->appname);
|
||||
git__free(process);
|
||||
}
|
||||
2
tests/resources/process/cat.bat
vendored
Normal file
2
tests/resources/process/cat.bat
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
@ECHO OFF
|
||||
FOR /F "tokens=*" %%a IN ('more') DO ECHO %%a
|
||||
2
tests/resources/process/env.cmd
vendored
Normal file
2
tests/resources/process/env.cmd
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
@ECHO OFF
|
||||
SET
|
||||
3
tests/resources/process/helloworld.sh
vendored
Executable file
3
tests/resources/process/helloworld.sh
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Hello, world."
|
||||
2
tests/resources/process/pwd.bat
vendored
Normal file
2
tests/resources/process/pwd.bat
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
@ECHO OFF
|
||||
ECHO %CD%
|
||||
111
tests/util/process/env.c
Normal file
111
tests/util/process/env.c
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "process.h"
|
||||
#include "vector.h"
|
||||
|
||||
static git_str env_cmd = GIT_STR_INIT;
|
||||
static git_str accumulator = GIT_STR_INIT;
|
||||
static git_vector env_result = GIT_VECTOR_INIT;
|
||||
|
||||
void test_process_env__initialize(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_str_printf(&env_cmd, "%s/env.cmd", cl_fixture("process"));
|
||||
#else
|
||||
git_str_puts(&env_cmd, "/usr/bin/env");
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_vector_init(&env_result, 32, git__strcmp_cb));
|
||||
}
|
||||
|
||||
void test_process_env__cleanup(void)
|
||||
{
|
||||
git_vector_free(&env_result);
|
||||
git_str_dispose(&accumulator);
|
||||
git_str_dispose(&env_cmd);
|
||||
}
|
||||
|
||||
static void run_env(const char **env_array, size_t env_len, bool exclude_env)
|
||||
{
|
||||
const char *args_array[] = { env_cmd.ptr };
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_process_result result = GIT_PROCESS_RESULT_INIT;
|
||||
|
||||
char buf[1024], *tok;
|
||||
ssize_t ret;
|
||||
|
||||
opts.capture_out = 1;
|
||||
opts.exclude_env = exclude_env;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), env_array, env_len, &opts));
|
||||
cl_git_pass(git_process_start(process));
|
||||
|
||||
while ((ret = git_process_read(process, buf, 1024)) > 0)
|
||||
cl_git_pass(git_str_put(&accumulator, buf, (size_t)ret));
|
||||
|
||||
cl_assert_equal_i(0, ret);
|
||||
|
||||
cl_git_pass(git_process_wait(&result, process));
|
||||
|
||||
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
|
||||
cl_assert_equal_i(0, result.exitcode);
|
||||
cl_assert_equal_i(0, result.signal);
|
||||
|
||||
for (tok = strtok(accumulator.ptr, "\n"); tok; tok = strtok(NULL, "\n")) {
|
||||
#ifdef GIT_WIN32
|
||||
if (strlen(tok) && tok[strlen(tok) - 1] == '\r')
|
||||
tok[strlen(tok) - 1] = '\0';
|
||||
#endif
|
||||
|
||||
cl_git_pass(git_vector_insert(&env_result, tok));
|
||||
}
|
||||
|
||||
git_process_close(process);
|
||||
git_process_free(process);
|
||||
}
|
||||
|
||||
void test_process_env__can_add_env(void)
|
||||
{
|
||||
const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" };
|
||||
run_env(env_array, 2, false);
|
||||
|
||||
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=added"));
|
||||
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_OTHER_ENV=also_added"));
|
||||
}
|
||||
|
||||
void test_process_env__can_propagate_env(void)
|
||||
{
|
||||
cl_setenv("TEST_NEW_ENV", "propagated");
|
||||
run_env(NULL, 0, false);
|
||||
|
||||
cl_git_pass(git_vector_search(NULL, &env_result, "TEST_NEW_ENV=propagated"));
|
||||
}
|
||||
|
||||
void test_process_env__can_remove_env(void)
|
||||
{
|
||||
const char *env_array[] = { "TEST_NEW_ENV=" };
|
||||
char *str;
|
||||
size_t i;
|
||||
|
||||
cl_setenv("TEST_NEW_ENV", "propagated");
|
||||
run_env(env_array, 1, false);
|
||||
|
||||
git_vector_foreach(&env_result, i, str)
|
||||
cl_assert(git__prefixcmp(str, "TEST_NEW_ENV=") != 0);
|
||||
}
|
||||
|
||||
void test_process_env__can_clear_env(void)
|
||||
{
|
||||
const char *env_array[] = { "TEST_NEW_ENV=added", "TEST_OTHER_ENV=also_added" };
|
||||
|
||||
cl_setenv("SOME_EXISTING_ENV", "propagated");
|
||||
run_env(env_array, 2, true);
|
||||
|
||||
/*
|
||||
* We can't simply test that the environment is precisely what we
|
||||
* provided. Some systems (eg win32) will add environment variables
|
||||
* to all processes.
|
||||
*/
|
||||
cl_assert_equal_i(GIT_ENOTFOUND, git_vector_search(NULL, &env_result, "SOME_EXISTING_ENV=propagated"));
|
||||
}
|
||||
212
tests/util/process/start.c
Normal file
212
tests/util/process/start.c
Normal file
@@ -0,0 +1,212 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "process.h"
|
||||
#include "vector.h"
|
||||
|
||||
#ifndef SIGPIPE
|
||||
# define SIGPIPE 42
|
||||
#endif
|
||||
|
||||
static git_str helloworld_cmd = GIT_STR_INIT;
|
||||
static git_str cat_cmd = GIT_STR_INIT;
|
||||
static git_str pwd_cmd = GIT_STR_INIT;
|
||||
|
||||
void test_process_start__initialize(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_str_printf(&helloworld_cmd, "%s/helloworld.bat", cl_fixture("process"));
|
||||
git_str_printf(&cat_cmd, "%s/cat.bat", cl_fixture("process"));
|
||||
git_str_printf(&pwd_cmd, "%s/pwd.bat", cl_fixture("process"));
|
||||
#else
|
||||
git_str_printf(&helloworld_cmd, "%s/helloworld.sh", cl_fixture("process"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_process_start__cleanup(void)
|
||||
{
|
||||
git_str_dispose(&pwd_cmd);
|
||||
git_str_dispose(&cat_cmd);
|
||||
git_str_dispose(&helloworld_cmd);
|
||||
}
|
||||
|
||||
void test_process_start__returncode(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", "exit", "1" };
|
||||
#elif __APPLE__
|
||||
const char *args_array[] = { "/usr/bin/false" };
|
||||
#else
|
||||
const char *args_array[] = { "/bin/false" };
|
||||
#endif
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_process_result result = GIT_PROCESS_RESULT_INIT;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_pass(git_process_start(process));
|
||||
cl_git_pass(git_process_wait(&result, process));
|
||||
|
||||
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
|
||||
cl_assert_equal_i(1, result.exitcode);
|
||||
cl_assert_equal_i(0, result.signal);
|
||||
|
||||
git_process_free(process);
|
||||
}
|
||||
|
||||
void test_process_start__not_found(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *args_array[] = { "C:\\a\\b\\z\\y\\not_found" };
|
||||
#else
|
||||
const char *args_array[] = { "/a/b/z/y/not_found" };
|
||||
#endif
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_fail(git_process_start(process));
|
||||
git_process_free(process);
|
||||
}
|
||||
|
||||
static void write_all(git_process *process, char *buf)
|
||||
{
|
||||
size_t buf_len = strlen(buf);
|
||||
ssize_t ret;
|
||||
|
||||
while (buf_len) {
|
||||
ret = git_process_write(process, buf, buf_len);
|
||||
cl_git_pass(ret < 0 ? (int)ret : 0);
|
||||
|
||||
buf += ret;
|
||||
buf_len -= ret;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_all(git_str *out, git_process *process)
|
||||
{
|
||||
char buf[32];
|
||||
size_t buf_len = 32;
|
||||
ssize_t ret;
|
||||
|
||||
while ((ret = git_process_read(process, buf, buf_len)) > 0)
|
||||
cl_git_pass(git_str_put(out, buf, ret));
|
||||
|
||||
cl_git_pass(ret < 0 ? (int)ret : 0);
|
||||
}
|
||||
|
||||
void test_process_start__redirect_stdio(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", cat_cmd.ptr };
|
||||
#else
|
||||
const char *args_array[] = { "/bin/cat" };
|
||||
#endif
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_process_result result = GIT_PROCESS_RESULT_INIT;
|
||||
git_str buf = GIT_STR_INIT;
|
||||
|
||||
opts.capture_in = 1;
|
||||
opts.capture_out = 1;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_pass(git_process_start(process));
|
||||
|
||||
write_all(process, "Hello, world.\r\nHello!\r\n");
|
||||
cl_git_pass(git_process_close_in(process));
|
||||
|
||||
read_all(&buf, process);
|
||||
cl_assert_equal_s("Hello, world.\r\nHello!\r\n", buf.ptr);
|
||||
|
||||
cl_git_pass(git_process_wait(&result, process));
|
||||
|
||||
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
|
||||
cl_assert_equal_i(0, result.exitcode);
|
||||
cl_assert_equal_i(0, result.signal);
|
||||
|
||||
git_str_dispose(&buf);
|
||||
git_process_free(process);
|
||||
}
|
||||
|
||||
void test_process_start__catch_signal(void)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
const char *args_array[] = { helloworld_cmd.ptr };
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_process_result result = GIT_PROCESS_RESULT_INIT;
|
||||
|
||||
opts.capture_out = 1;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_pass(git_process_start(process));
|
||||
cl_git_pass(git_process_close(process));
|
||||
cl_git_pass(git_process_wait(&result, process));
|
||||
|
||||
cl_assert_equal_i(GIT_PROCESS_STATUS_ERROR, result.status);
|
||||
cl_assert_equal_i(0, result.exitcode);
|
||||
cl_assert_equal_i(SIGPIPE, result.signal);
|
||||
|
||||
git_process_free(process);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_process_start__can_chdir(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr };
|
||||
char *startwd = "C:\\";
|
||||
#else
|
||||
const char *args_array[] = { "/bin/pwd" };
|
||||
char *startwd = "/";
|
||||
#endif
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
git_process_result result = GIT_PROCESS_RESULT_INIT;
|
||||
git_str buf = GIT_STR_INIT;
|
||||
|
||||
opts.cwd = startwd;
|
||||
opts.capture_out = 1;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_pass(git_process_start(process));
|
||||
|
||||
read_all(&buf, process);
|
||||
git_str_rtrim(&buf);
|
||||
|
||||
cl_assert_equal_s(startwd, buf.ptr);
|
||||
|
||||
cl_git_pass(git_process_wait(&result, process));
|
||||
|
||||
cl_assert_equal_i(GIT_PROCESS_STATUS_NORMAL, result.status);
|
||||
cl_assert_equal_i(0, result.exitcode);
|
||||
cl_assert_equal_i(0, result.signal);
|
||||
|
||||
git_str_dispose(&buf);
|
||||
git_process_free(process);
|
||||
}
|
||||
|
||||
void test_process_start__cannot_chdir_to_nonexistent_dir(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *args_array[] = { "C:\\Windows\\System32\\cmd.exe", "/c", pwd_cmd.ptr };
|
||||
char *startwd = "C:\\a\\b\\z\\y\\not_found";
|
||||
#else
|
||||
const char *args_array[] = { "/bin/pwd" };
|
||||
char *startwd = "/a/b/z/y/not_found";
|
||||
#endif
|
||||
|
||||
git_process *process;
|
||||
git_process_options opts = GIT_PROCESS_OPTIONS_INIT;
|
||||
|
||||
opts.cwd = startwd;
|
||||
opts.capture_out = 1;
|
||||
|
||||
cl_git_pass(git_process_new(&process, args_array, ARRAY_SIZE(args_array), NULL, 0, &opts));
|
||||
cl_git_fail(git_process_start(process));
|
||||
git_process_free(process);
|
||||
}
|
||||
61
tests/util/process/win32.c
Normal file
61
tests/util/process/win32.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "process.h"
|
||||
#include "vector.h"
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
static git_str result;
|
||||
|
||||
# define assert_cmdline(expected, given) do { \
|
||||
cl_git_pass(git_process__cmdline(&result, given, ARRAY_SIZE(given))); \
|
||||
cl_assert_equal_s(expected, result.ptr); \
|
||||
git_str_dispose(&result); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
void test_process_win32__cmdline_is_whitespace_delimited(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *one[] = { "one" };
|
||||
const char *two[] = { "one", "two" };
|
||||
const char *three[] = { "one", "two", "three" };
|
||||
const char *four[] = { "one", "two", "three", "four" };
|
||||
|
||||
assert_cmdline("one", one);
|
||||
assert_cmdline("one two", two);
|
||||
assert_cmdline("one two three", three);
|
||||
assert_cmdline("one two three four", four);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_process_win32__cmdline_escapes_whitespace(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *spaces[] = { "one with spaces" };
|
||||
const char *tabs[] = { "one\twith\ttabs" };
|
||||
const char *multiple[] = { "one with many spaces" };
|
||||
|
||||
assert_cmdline("one\" \"with\" \"spaces", spaces);
|
||||
assert_cmdline("one\"\t\"with\"\t\"tabs", tabs);
|
||||
assert_cmdline("one\" \"with\" \"many\" \"spaces", multiple);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_process_win32__cmdline_escapes_quotes(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *one[] = { "echo", "\"hello world\"" };
|
||||
|
||||
assert_cmdline("echo \\\"hello\" \"world\\\"", one);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_process_win32__cmdline_escapes_backslash(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
const char *one[] = { "foo\\bar", "foo\\baz" };
|
||||
const char *two[] = { "c:\\program files\\foo bar\\foo bar.exe", "c:\\path\\to\\other\\", "/a", "/b" };
|
||||
|
||||
assert_cmdline("foo\\\\bar foo\\\\baz", one);
|
||||
assert_cmdline("c:\\\\program\" \"files\\\\foo\" \"bar\\\\foo\" \"bar.exe c:\\\\path\\\\to\\\\other\\\\ /a /b", two);
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user