diff options
Diffstat (limited to 'src/libstrongswan')
-rw-r--r-- | src/libstrongswan/Android.mk | 2 | ||||
-rw-r--r-- | src/libstrongswan/Makefile.am | 4 | ||||
-rw-r--r-- | src/libstrongswan/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/libstrongswan/tests/suites/test_process.c | 221 | ||||
-rw-r--r-- | src/libstrongswan/tests/tests.h | 1 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.c | 591 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.h | 97 |
7 files changed, 914 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..c22c47294 --- /dev/null +++ b/src/libstrongswan/tests/suites/test_process.c @@ -0,0 +1,221 @@ +/* + * 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[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "exit 0", +#else + "/bin/true", +#endif + 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[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "exit 1", +#else + "/bin/false", +#endif + 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[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\more.com", +#else + "/bin/cat", +#endif + 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[] = { +#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; + 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[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "echo %A% %B%", +#else + "/bin/sh", + "-c", + "echo -n $A $B", +#endif + 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); +#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); +} +END_TEST + +START_TEST(test_shell) +{ + process_t *process; + int retval; + + process = process_start_shell(NULL, NULL, NULL, NULL, "exit %d", 3); + ck_assert(process != NULL); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 3); +} +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); + + tc = tcase_create("shell"); + tcase_add_test(tc, test_shell); + 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..233429447 --- /dev/null +++ b/src/libstrongswan/utils/process.c @@ -0,0 +1,591 @@ +/* + * 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. + */ + +/* vasprintf() */ +#define _GNU_SOURCE +#include "process.h" + +#include <library.h> +#include <utils/debug.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdarg.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; + } +} + +/** + * See header + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...) +{ + char *argv[] = { + "/bin/sh", + "-c", + NULL, + NULL + }; + process_t *process; + va_list args; + int len; + + va_start(args, fmt); + len = vasprintf(&argv[2], fmt, args); + va_end(args); + if (len < 0) + { + return NULL; + } + + process = process_start(argv, envp, in, out, err, TRUE); + free(argv[2]); + return process; +} + +#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) +{ + 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; +} + +/** + * See header + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...) +{ + char path[MAX_PATH], *exe = "system32\\cmd.exe"; + char *argv[] = { + path, + "/C", + NULL, + NULL + }; + process_t *process; + va_list args; + int len; + + len = GetSystemWindowsDirectory(path, sizeof(path)); + if (len == 0 || len >= sizeof(path) - strlen(exe)) + { + DBG1(DBG_LIB, "resolving Windows directory failed: 0x%08x", + GetLastError()); + return NULL; + } + if (path[len + 1] != '\\') + { + strncat(path, "\\", sizeof(path) - len++); + } + strncat(path, exe, sizeof(path) - len); + + va_start(args, fmt); + len = vasprintf(&argv[2], fmt, args); + va_end(args); + if (len < 0) + { + return NULL; + } + + process = process_start(argv, envp, in, out, err, TRUE); + free(argv[2]); + return process; +} + +#endif /* WIN32 */ diff --git a/src/libstrongswan/utils/process.h b/src/libstrongswan/utils/process.h new file mode 100644 index 000000000..81719201c --- /dev/null +++ b/src/libstrongswan/utils/process.h @@ -0,0 +1,97 @@ +/* + * 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); + +/** + * Spawn a command in a shell child process. + * + * Same as process_start(), but passes a single command to a shell, such as + * "sh -c". See process_start() for I/O redirection notes. + * + * @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 fmt printf format string for command + * @param ... arguments for fmt + * @return process, NULL on failure + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...); + +#endif /** PROCESS_H_ @}*/ |