process: Port child process spawning to the Windows platform

This commit is contained in:
Martin Willi
2014-10-06 18:24:39 +02:00
parent 1fea589c1f
commit 02e4dedce5
2 changed files with 315 additions and 1 deletions
@@ -23,7 +23,13 @@ START_TEST(test_retval_true)
{
process_t *process;
char *argv[] = {
#ifdef WIN32
"C:\\Windows\\system32\\cmd.exe",
"/C",
"exit 0",
#else
"/bin/true",
#endif
NULL
};
int retval;
@@ -39,7 +45,13 @@ START_TEST(test_retval_false)
{
process_t *process;
char *argv[] = {
#ifdef WIN32
"C:\\Windows\\system32\\cmd.exe",
"/C",
"exit 1",
#else
"/bin/false",
#endif
NULL
};
int retval;
@@ -69,7 +81,11 @@ START_TEST(test_echo)
{
process_t *process;
char *argv[] = {
#ifdef WIN32
"C:\\Windows\\system32\\more.com",
#else
"/bin/cat",
#endif
NULL
};
int retval, in, out;
@@ -94,9 +110,15 @@ START_TEST(test_echo_err)
{
process_t *process;
char *argv[] = {
#ifdef WIN32
"C:\\Windows\\system32\\cmd.exe",
"/C",
"1>&2 C:\\Windows\\system32\\more.com",
#else
"/bin/sh",
"-c",
"1>&2 /bin/cat",
#endif
NULL
};
int retval, in, err;
@@ -121,9 +143,15 @@ START_TEST(test_env)
{
process_t *process;
char *argv[] = {
#ifdef WIN32
"C:\\Windows\\system32\\cmd.exe",
"/C",
"echo %A% %B%",
#else
"/bin/sh",
"-c",
"echo -n $A $B",
#endif
NULL
};
char *envp[] = {
@@ -137,7 +165,11 @@ START_TEST(test_env)
process = process_start(argv, envp, NULL, &out, NULL, TRUE);
ck_assert(process != NULL);
ck_assert(read(out, buf, sizeof(buf)) > 0);
#ifdef WIN32
ck_assert_str_eq(buf, "atest bstring\r\n");
#else
ck_assert_str_eq(buf, "atest bstring");
#endif
ck_assert(close(out) == 0);
ck_assert(process->wait(process, &retval));
ck_assert_int_eq(retval, 0);
+283 -1
View File
@@ -220,13 +220,295 @@ process_t* process_start(char *const argv[], char *const envp[],
#else /* WIN32 */
/**
* Private data of an process_t object.
*/
struct private_process_t {
/**
* Public process_t interface.
*/
process_t public;
/**
* child stdin pipe
*/
HANDLE in[PIPE_ENDS];
/**
* child stdout pipe
*/
HANDLE out[PIPE_ENDS];
/**
* child stderr pipe
*/
HANDLE err[PIPE_ENDS];
/**
* child process information
*/
PROCESS_INFORMATION pi;
};
/**
* Clean up state associated to child process
*/
static void process_destroy(private_process_t *this)
{
if (this->in[PIPE_READ])
{
CloseHandle(this->in[PIPE_READ]);
}
if (this->in[PIPE_WRITE])
{
CloseHandle(this->in[PIPE_WRITE]);
}
if (this->out[PIPE_READ])
{
CloseHandle(this->out[PIPE_READ]);
}
if (this->out[PIPE_WRITE])
{
CloseHandle(this->out[PIPE_WRITE]);
}
if (this->err[PIPE_READ])
{
CloseHandle(this->err[PIPE_READ]);
}
if (this->err[PIPE_WRITE])
{
CloseHandle(this->err[PIPE_WRITE]);
}
if (this->pi.hProcess)
{
CloseHandle(this->pi.hProcess);
CloseHandle(this->pi.hThread);
}
free(this);
}
METHOD(process_t, wait_, bool,
private_process_t *this, int *code)
{
DWORD ec;
if (WaitForSingleObject(this->pi.hProcess, INFINITE) != WAIT_OBJECT_0)
{
DBG1(DBG_LIB, "waiting for child process failed: 0x%08x",
GetLastError());
process_destroy(this);
return FALSE;
}
if (code)
{
if (!GetExitCodeProcess(this->pi.hProcess, &ec))
{
DBG1(DBG_LIB, "getting child process exit code failed: 0x%08x",
GetLastError());
process_destroy(this);
return FALSE;
}
*code = ec;
}
process_destroy(this);
return TRUE;
}
/**
* Append a command line argument to buf, optionally quoted
*/
static void append_arg(char *buf, u_int len, char *arg, char *quote)
{
char *space = "";
int current;
current = strlen(buf);
if (current)
{
space = " ";
}
snprintf(buf + current, len - current, "%s%s%s%s", space, quote, arg, quote);
}
/**
* Append a null-terminate env string to buf
*/
static void append_env(char *buf, u_int len, char *env)
{
char *pos = buf;
int current;
while (TRUE)
{
pos += strlen(pos);
if (!pos[1])
{
if (pos == buf)
{
current = 0;
}
else
{
current = pos - buf + 1;
}
snprintf(buf + current, len - current, "%s", env);
break;
}
pos++;
}
}
/**
* See header
*/
process_t* process_start(char *const argv[], char *const envp[],
int *in, int *out, int *err, bool close_all)
{
return NULL;
private_process_t *this;
char arg[32768], env[32768];
SECURITY_ATTRIBUTES sa = {
.nLength = sizeof(SECURITY_ATTRIBUTES),
.bInheritHandle = TRUE,
};
STARTUPINFO sui = {
.cb = sizeof(STARTUPINFO),
};
int i;
memset(arg, 0, sizeof(arg));
memset(env, 0, sizeof(env));
for (i = 0; argv[i]; i++)
{
if (!strchr(argv[i], ' '))
{ /* no spaces, fine for appending */
append_arg(arg, sizeof(arg) - 1, argv[i], "");
}
else if (argv[i][0] == '"' &&
argv[i][strlen(argv[i]) - 1] == '"' &&
strchr(argv[i] + 1, '"') == argv[i] + strlen(argv[i]) - 1)
{ /* already properly quoted */
append_arg(arg, sizeof(arg) - 1, argv[i], "");
}
else if (strchr(argv[i], ' ') && !strchr(argv[i], '"'))
{ /* spaces, but no quotes; append quoted */
append_arg(arg, sizeof(arg) - 1, argv[i], "\"");
}
else
{
DBG1(DBG_LIB, "invalid command line argument: %s", argv[i]);
return NULL;
}
}
if (envp)
{
for (i = 0; envp[i]; i++)
{
append_env(env, sizeof(env) - 1, envp[i]);
}
}
INIT(this,
.public = {
.wait = _wait_,
},
);
if (in)
{
sui.dwFlags = STARTF_USESTDHANDLES;
if (!CreatePipe(&this->in[PIPE_READ], &this->in[PIPE_WRITE], &sa, 0))
{
process_destroy(this);
return NULL;
}
if (!SetHandleInformation(this->in[PIPE_WRITE], HANDLE_FLAG_INHERIT, 0))
{
process_destroy(this);
return NULL;
}
sui.hStdInput = this->in[PIPE_READ];
*in = _open_osfhandle((uintptr_t)this->in[PIPE_WRITE], 0);
if (*in == -1)
{
process_destroy(this);
return NULL;
}
}
if (out)
{
sui.dwFlags = STARTF_USESTDHANDLES;
if (!CreatePipe(&this->out[PIPE_READ], &this->out[PIPE_WRITE], &sa, 0))
{
process_destroy(this);
return NULL;
}
if (!SetHandleInformation(this->out[PIPE_READ], HANDLE_FLAG_INHERIT, 0))
{
process_destroy(this);
return NULL;
}
sui.hStdOutput = this->out[PIPE_WRITE];
*out = _open_osfhandle((uintptr_t)this->out[PIPE_READ], 0);
if (*out == -1)
{
process_destroy(this);
return NULL;
}
}
if (err)
{
sui.dwFlags = STARTF_USESTDHANDLES;
if (!CreatePipe(&this->err[PIPE_READ], &this->err[PIPE_WRITE], &sa, 0))
{
process_destroy(this);
return NULL;
}
if (!SetHandleInformation(this->err[PIPE_READ], HANDLE_FLAG_INHERIT, 0))
{
process_destroy(this);
return NULL;
}
sui.hStdError = this->err[PIPE_WRITE];
*err = _open_osfhandle((uintptr_t)this->err[PIPE_READ], 0);
if (*err == -1)
{
process_destroy(this);
return NULL;
}
}
if (!CreateProcess(argv[0], arg, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS, env, NULL, &sui, &this->pi))
{
DBG1(DBG_LIB, "creating process '%s' failed: 0x%08x",
argv[0], GetLastError());
process_destroy(this);
return NULL;
}
/* close child process end of pipes */
if (this->in[PIPE_READ])
{
CloseHandle(this->in[PIPE_READ]);
this->in[PIPE_READ] = NULL;
}
if (this->out[PIPE_WRITE])
{
CloseHandle(this->out[PIPE_WRITE]);
this->out[PIPE_WRITE] = NULL;
}
if (this->err[PIPE_WRITE])
{
CloseHandle(this->err[PIPE_WRITE]);
this->err[PIPE_WRITE] = NULL;
}
/* our side gets closed over the osf_handle closed by caller */
this->in[PIPE_WRITE] = NULL;
this->out[PIPE_READ] = NULL;
this->err[PIPE_READ] = NULL;
return &this->public;
}
#endif /* WIN32 */