aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Willi <martin@revosec.ch>2014-10-02 16:17:46 +0200
committerMartin Willi <martin@revosec.ch>2014-10-06 18:24:39 +0200
commit1fea589c1fbb0457ba4aae3adb2577d2ed257b61 (patch)
treec38ff6b9c80fdab6323e6628dc77418196044f74
parent100c1a4bf1f59414af8a1bfb6acb14b1a98e3349 (diff)
downloadstrongswan-1fea589c1fbb0457ba4aae3adb2577d2ed257b61.tar.bz2
strongswan-1fea589c1fbb0457ba4aae3adb2577d2ed257b61.tar.xz
process: Provide an abstraction to spawn child processes with redirected I/O
-rw-r--r--src/libstrongswan/Android.mk2
-rw-r--r--src/libstrongswan/Makefile.am4
-rw-r--r--src/libstrongswan/tests/Makefile.am1
-rw-r--r--src/libstrongswan/tests/suites/test_process.c173
-rw-r--r--src/libstrongswan/tests/tests.h1
-rw-r--r--src/libstrongswan/utils/process.c232
-rw-r--r--src/libstrongswan/utils/process.h80
7 files changed, 490 insertions, 3 deletions
diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk
index 3ddd42f11..9b775f9b3 100644
--- a/src/libstrongswan/Android.mk
+++ b/src/libstrongswan/Android.mk
@@ -37,7 +37,7 @@ selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
settings/settings_parser.c settings/settings_lexer.c \
utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \
-utils/parser_helper.c utils/test.c utils/utils/strerror.c
+utils/parser_helper.c utils/test.c utils/process.c utils/utils/strerror.c
libstrongswan_la_SOURCES += \
threading/thread.c \
diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am
index 3fb57de5a..0083ffe6b 100644
--- a/src/libstrongswan/Makefile.am
+++ b/src/libstrongswan/Makefile.am
@@ -35,7 +35,7 @@ selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
settings/settings_parser.y settings/settings_lexer.l \
utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \
-utils/parser_helper.c utils/test.c utils/utils/strerror.c
+utils/parser_helper.c utils/test.c utils/process.c utils/utils/strerror.c
if !USE_WINDOWS
libstrongswan_la_SOURCES += \
@@ -102,7 +102,7 @@ utils/lexparser.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h \
utils/leak_detective.h utils/printf_hook/printf_hook.h \
utils/printf_hook/printf_hook_vstr.h utils/printf_hook/printf_hook_builtin.h \
utils/parser_helper.h utils/test.h utils/integrity_checker.h utils/windows.h \
-utils/utils/strerror.h
+utils/process.h utils/utils/strerror.h
endif
library.lo : $(top_builddir)/config.status
diff --git a/src/libstrongswan/tests/Makefile.am b/src/libstrongswan/tests/Makefile.am
index e8e8090f3..7ecba19da 100644
--- a/src/libstrongswan/tests/Makefile.am
+++ b/src/libstrongswan/tests/Makefile.am
@@ -30,6 +30,7 @@ tests_SOURCES = tests.h tests.c \
suites/test_hashtable.c \
suites/test_identification.c \
suites/test_threading.c \
+ suites/test_process.c \
suites/test_watcher.c \
suites/test_stream.c \
suites/test_fetch_http.c \
diff --git a/src/libstrongswan/tests/suites/test_process.c b/src/libstrongswan/tests/suites/test_process.c
new file mode 100644
index 000000000..e41b01535
--- /dev/null
+++ b/src/libstrongswan/tests/suites/test_process.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "test_suite.h"
+
+#include <unistd.h>
+
+#include <utils/process.h>
+
+START_TEST(test_retval_true)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/true",
+ NULL
+ };
+ int retval;
+
+ process = process_start(argv, NULL, NULL, NULL, NULL, TRUE);
+ ck_assert(process != NULL);
+ ck_assert(process->wait(process, &retval));
+ ck_assert_int_eq(retval, 0);
+}
+END_TEST
+
+START_TEST(test_retval_false)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/false",
+ NULL
+ };
+ int retval;
+
+ process = process_start(argv, NULL, NULL, NULL, NULL, TRUE);
+ ck_assert(process != NULL);
+ ck_assert(process->wait(process, &retval));
+ ck_assert(retval != 0);
+}
+END_TEST
+
+START_TEST(test_not_found)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/does-not-exist",
+ NULL
+ };
+
+ process = process_start(argv, NULL, NULL, NULL, NULL, TRUE);
+ /* both is acceptable behavior */
+ ck_assert(process == NULL || !process->wait(process, NULL));
+}
+END_TEST
+
+START_TEST(test_echo)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/cat",
+ NULL
+ };
+ int retval, in, out;
+ char *msg = "test";
+ char buf[strlen(msg) + 1];
+
+ memset(buf, 0, strlen(msg) + 1);
+
+ process = process_start(argv, NULL, &in, &out, NULL, TRUE);
+ ck_assert(process != NULL);
+ ck_assert_int_eq(write(in, msg, strlen(msg)), strlen(msg));
+ ck_assert(close(in) == 0);
+ ck_assert_int_eq(read(out, buf, strlen(msg) + 1), strlen(msg));
+ ck_assert_str_eq(buf, msg);
+ ck_assert(close(out) == 0);
+ ck_assert(process->wait(process, &retval));
+ ck_assert_int_eq(retval, 0);
+}
+END_TEST
+
+START_TEST(test_echo_err)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/sh",
+ "-c",
+ "1>&2 /bin/cat",
+ NULL
+ };
+ int retval, in, err;
+ char *msg = "a longer test message";
+ char buf[strlen(msg) + 1];
+
+ memset(buf, 0, strlen(msg) + 1);
+
+ process = process_start(argv, NULL, &in, NULL, &err, TRUE);
+ ck_assert(process != NULL);
+ ck_assert_int_eq(write(in, msg, strlen(msg)), strlen(msg));
+ ck_assert(close(in) == 0);
+ ck_assert_int_eq(read(err, buf, strlen(msg) + 1), strlen(msg));
+ ck_assert_str_eq(buf, msg);
+ ck_assert(close(err) == 0);
+ ck_assert(process->wait(process, &retval));
+ ck_assert_int_eq(retval, 0);
+}
+END_TEST
+
+START_TEST(test_env)
+{
+ process_t *process;
+ char *argv[] = {
+ "/bin/sh",
+ "-c",
+ "echo -n $A $B",
+ NULL
+ };
+ char *envp[] = {
+ "A=atest",
+ "B=bstring",
+ NULL
+ };
+ int retval, out;
+ char buf[64] = {};
+
+ process = process_start(argv, envp, NULL, &out, NULL, TRUE);
+ ck_assert(process != NULL);
+ ck_assert(read(out, buf, sizeof(buf)) > 0);
+ ck_assert_str_eq(buf, "atest bstring");
+ ck_assert(close(out) == 0);
+ ck_assert(process->wait(process, &retval));
+ ck_assert_int_eq(retval, 0);
+}
+END_TEST
+
+Suite *process_suite_create()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("process");
+
+ tc = tcase_create("return values");
+ tcase_add_test(tc, test_retval_true);
+ tcase_add_test(tc, test_retval_false);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("not found");
+ tcase_add_test(tc, test_not_found);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("echo");
+ tcase_add_test(tc, test_echo);
+ tcase_add_test(tc, test_echo_err);
+ suite_add_tcase(s, tc);
+
+ tc = tcase_create("env");
+ tcase_add_test(tc, test_env);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
diff --git a/src/libstrongswan/tests/tests.h b/src/libstrongswan/tests/tests.h
index ab0f642e4..586227800 100644
--- a/src/libstrongswan/tests/tests.h
+++ b/src/libstrongswan/tests/tests.h
@@ -24,6 +24,7 @@ TEST_SUITE(hashtable_suite_create)
TEST_SUITE(array_suite_create)
TEST_SUITE(identification_suite_create)
TEST_SUITE(threading_suite_create)
+TEST_SUITE(process_suite_create)
TEST_SUITE(watcher_suite_create)
TEST_SUITE(stream_suite_create)
TEST_SUITE(utils_suite_create)
diff --git a/src/libstrongswan/utils/process.c b/src/libstrongswan/utils/process.c
new file mode 100644
index 000000000..bcd8c7690
--- /dev/null
+++ b/src/libstrongswan/utils/process.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "process.h"
+
+#include <utils/debug.h>
+
+#include <fcntl.h>
+
+typedef struct private_process_t private_process_t;
+
+/**
+ * Ends of a pipe()
+ */
+enum {
+ PIPE_READ = 0,
+ PIPE_WRITE = 1,
+ PIPE_ENDS,
+};
+
+#ifndef WIN32
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+/**
+ * Private data of an process_t object.
+ */
+struct private_process_t {
+
+ /**
+ * Public process_t interface.
+ */
+ process_t public;
+
+ /**
+ * child stdin pipe
+ */
+ int in[PIPE_ENDS];
+
+ /**
+ * child stdout pipe
+ */
+ int out[PIPE_ENDS];
+
+ /**
+ * child stderr pipe
+ */
+ int err[PIPE_ENDS];
+
+ /**
+ * child process
+ */
+ int pid;
+};
+
+/**
+ * Close a file descriptor if it is not -1
+ */
+static void close_if(int *fd)
+{
+ if (*fd != -1)
+ {
+ close(*fd);
+ *fd = -1;
+ }
+}
+
+/**
+ * Destroy a process structure, close all pipes
+ */
+static void process_destroy(private_process_t *this)
+{
+ close_if(&this->in[PIPE_READ]);
+ close_if(&this->in[PIPE_WRITE]);
+ close_if(&this->out[PIPE_READ]);
+ close_if(&this->out[PIPE_WRITE]);
+ close_if(&this->err[PIPE_READ]);
+ close_if(&this->err[PIPE_WRITE]);
+ free(this);
+}
+
+METHOD(process_t, wait_, bool,
+ private_process_t *this, int *code)
+{
+ int status, ret;
+
+ ret = waitpid(this->pid, &status, 0);
+ process_destroy(this);
+ if (ret == -1)
+ {
+ return FALSE;
+ }
+ if (!WIFEXITED(status))
+ {
+ return FALSE;
+ }
+ if (code)
+ {
+ *code = WEXITSTATUS(status);
+ }
+ return TRUE;
+}
+
+/**
+ * See header
+ */
+process_t* process_start(char *const argv[], char *const envp[],
+ int *in, int *out, int *err, bool close_all)
+{
+ private_process_t *this;
+ char *empty[] = { NULL };
+
+ INIT(this,
+ .public = {
+ .wait = _wait_,
+ },
+ .in = { -1, -1 },
+ .out = { -1, -1 },
+ .err = { -1, -1 },
+ );
+
+ if (in && pipe(this->in) != 0)
+ {
+ DBG1(DBG_LIB, "creating stdin pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+ if (out && pipe(this->out) != 0)
+ {
+ DBG1(DBG_LIB, "creating stdout pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+ if (err && pipe(this->err) != 0)
+ {
+ DBG1(DBG_LIB, "creating stderr pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+
+ this->pid = fork();
+ switch (this->pid)
+ {
+ case -1:
+ DBG1(DBG_LIB, "forking process failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ case 0:
+ /* child */
+ close_if(&this->in[PIPE_WRITE]);
+ close_if(&this->out[PIPE_READ]);
+ close_if(&this->err[PIPE_READ]);
+ if (this->in[PIPE_READ] != -1)
+ {
+ if (dup2(this->in[PIPE_READ], 0) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (this->out[PIPE_WRITE] != -1)
+ {
+ if (dup2(this->out[PIPE_WRITE], 1) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (this->err[PIPE_WRITE] != -1)
+ {
+ if (dup2(this->err[PIPE_WRITE], 2) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (close_all)
+ {
+ closefrom(3);
+ }
+ if (execve(argv[0], argv, envp ?: empty) == -1)
+ {
+ raise(SIGKILL);
+ }
+ /* not reached */
+ default:
+ /* parent */
+ close_if(&this->in[PIPE_READ]);
+ close_if(&this->out[PIPE_WRITE]);
+ close_if(&this->err[PIPE_WRITE]);
+ if (in)
+ {
+ *in = this->in[PIPE_WRITE];
+ this->in[PIPE_WRITE] = -1;
+ }
+ if (out)
+ {
+ *out = this->out[PIPE_READ];
+ this->out[PIPE_READ] = -1;
+ }
+ if (err)
+ {
+ *err = this->err[PIPE_READ];
+ this->err[PIPE_READ] = -1;
+ }
+ return &this->public;
+ }
+}
+
+#else /* WIN32 */
+
+/**
+ * See header
+ */
+process_t* process_start(char *const argv[], char *const envp[],
+ int *in, int *out, int *err, bool close_all)
+{
+ return NULL;
+}
+
+#endif /* WIN32 */
diff --git a/src/libstrongswan/utils/process.h b/src/libstrongswan/utils/process.h
new file mode 100644
index 000000000..62d2ce757
--- /dev/null
+++ b/src/libstrongswan/utils/process.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**
+ * @defgroup process process
+ * @{ @ingroup utils
+ */
+
+#ifndef PROCESS_H_
+#define PROCESS_H_
+
+#include <utils/utils.h>
+
+typedef struct process_t process_t;
+
+/**
+ * Child process spawning abstraction
+ */
+struct process_t {
+
+ /**
+ * Wait for a started process to terminate.
+ *
+ * The process object gets destroyed by this call, regardless of the
+ * return value.
+ *
+ * The returned code is the exit code, not the status returned by waitpid().
+ * If the program could not be executed or has terminated abnormally
+ * (by signals etc.), FALSE is returned.
+ *
+ * @param code process exit code, set only if TRUE returned
+ * @return TRUE if program exited normally through exit()
+ */
+ bool (*wait)(process_t *this, int *code);
+};
+
+/**
+ * Spawn a child process with redirected I/O.
+ *
+ * Forks the current process, optionally redirects stdin/out/err to the current
+ * process, and executes the provided program with arguments.
+ *
+ * The process to execute is specified as argv[0], followed by the process
+ * arguments, followed by NULL. envp[] has a NULL terminated list of arguments
+ * to invoke the process with.
+ *
+ * If any of in/out/err is given, stdin/out/err from the child process get
+ * connected over pipe()s to the caller. If close_all is TRUE, all other
+ * open file descriptors get closed, regardless of any CLOEXEC setting.
+ *
+ * A caller must close all of the returned file descriptors to avoid file
+ * descriptor leaks.
+ *
+ * A non-NULL return value does not guarantee that the process has been
+ * invoked successfully.
+ *
+ * @param argv NULL terminated process arguments, with argv[0] as program
+ * @param envp NULL terminated list of environment variables
+ * @param in pipe fd returned for redirecting data to child stdin
+ * @param out pipe fd returned to redirect child stdout data to
+ * @param err pipe fd returned to redirect child stderr data to
+ * @param close_all close all open file descriptors above 2 before execve()
+ * @return process, NULL on failure
+ */
+process_t* process_start(char *const argv[], char *const envp[],
+ int *in, int *out, int *err, bool close_all);
+
+#endif /** PROCESS_H_ @}*/