From: Martin Willi Date: Fri, 3 Oct 2014 15:30:19 +0000 (+0200) Subject: process: Port child process spawning to the Windows platform X-Git-Tag: 5.2.1dr1^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=02e4dedce513c07da88b873acc6bd5e59dd867ae;p=thirdparty%2Fstrongswan.git process: Port child process spawning to the Windows platform --- diff --git a/src/libstrongswan/tests/suites/test_process.c b/src/libstrongswan/tests/suites/test_process.c index e41b01535f..7092f06509 100644 --- a/src/libstrongswan/tests/suites/test_process.c +++ b/src/libstrongswan/tests/suites/test_process.c @@ -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); diff --git a/src/libstrongswan/utils/process.c b/src/libstrongswan/utils/process.c index bcd8c76907..25e161b2cf 100644 --- a/src/libstrongswan/utils/process.c +++ b/src/libstrongswan/utils/process.c @@ -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 */