process: Port child process spawning to the Windows platform
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user