process: Port child process spawning to the Windows platform
authorMartin Willi <martin@revosec.ch>
Fri, 3 Oct 2014 15:30:19 +0000 (17:30 +0200)
committerMartin Willi <martin@revosec.ch>
Mon, 6 Oct 2014 16:24:39 +0000 (18:24 +0200)
src/libstrongswan/tests/suites/test_process.c
src/libstrongswan/utils/process.c

index e41b015..7092f06 100644 (file)
@@ -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);
index bcd8c76..25e161b 100644 (file)
@@ -221,12 +221,294 @@ 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 */