diff --git a/src/ddsrt/CMakeLists.txt b/src/ddsrt/CMakeLists.txt index 404f425..5b49248 100644 --- a/src/ddsrt/CMakeLists.txt +++ b/src/ddsrt/CMakeLists.txt @@ -74,7 +74,6 @@ list(APPEND sources "${source_path}/io.c" "${source_path}/log.c" "${source_path}/retcode.c" - "${source_path}/process.c" "${source_path}/strtod.c" "${source_path}/strtol.c") @@ -104,7 +103,7 @@ list(APPEND sources # network stack. In order to mix-and-match various compilers, architectures, # operating systems, etc input from the build system is required. foreach(feature atomics cdtors environ heap ifaddrs random rusage - sockets string sync threads time md5) + sockets string sync threads time md5 process) if(EXISTS "${include_path}/dds/ddsrt/${feature}.h") list(APPEND headers "${include_path}/dds/ddsrt/${feature}.h") file(GLOB diff --git a/src/ddsrt/include/dds/ddsrt/process.h b/src/ddsrt/include/dds/ddsrt/process.h index b2be585..45e7099 100644 --- a/src/ddsrt/include/dds/ddsrt/process.h +++ b/src/ddsrt/include/dds/ddsrt/process.h @@ -13,11 +13,9 @@ #define DDSRT_PROCESS_H #include "dds/export.h" +#include "dds/ddsrt/time.h" #include "dds/ddsrt/types.h" - -#if defined (__cplusplus) -extern "C" { -#endif +#include "dds/ddsrt/retcode.h" #if defined(_WIN32) typedef DWORD ddsrt_pid_t; @@ -33,6 +31,11 @@ typedef pid_t ddsrt_pid_t; #endif #endif /* _WIN32 */ + +#if defined (__cplusplus) +extern "C" { +#endif + /** * @brief Return process ID (PID) of the calling process. * @@ -41,6 +44,168 @@ typedef pid_t ddsrt_pid_t; DDS_EXPORT ddsrt_pid_t ddsrt_getpid(void); + +/** + * @brief Create new process. + * + * Creates a new process using the provided executable file. It will have + * default priority and scheduling. + * + * Process arguments are represented by argv, which can be null. If argv is + * not null, then the array must be null terminated. The argv array only has + * to contain the arguments, the executable filename doesn't have to be in + * the first element, which is normally the convention. + * + * @param[in] executable Executable file name. + * @param[in] argv Arguments array. + * @param[out] pid ID of the created process. + * + * @returns A dds_retcode_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Process successfully created. + * @retval DDS_RETCODE_BAD_PARAMETER + * Provided file is not available or not executable. + * @retval DDS_RETCODE_NOT_ALLOWED + * Caller is not permitted to start the process. + * @retval DDS_RETCODE_OUT_OF_RESOURCES + * Not enough resources to start the process. + * @retval DDS_RETCODE_ERROR + * Process could not be created. + */ +DDS_EXPORT dds_retcode_t +ddsrt_proc_create( + const char *executable, + char *const argv[], + ddsrt_pid_t *pid); + +/** + * @brief Wait for a specific child process to have finished. + * + * This function takes a process id and will wait until the related process + * has finished or the timeout is reached. + * + * If the process finished, then the exit code of that process will be copied + * into the given 'code' argument. + * + * Internally, the timeout can be round-up to the nearest milliseconds or + * seconds, depending on the platform. + * + * See ddsrt_proc_waitpids() for waiting on all child processes. + * + * @param[in] pid Process ID (PID) to get the exit code from. + * @param[in] timemout Time within the process is expected to finish. + * @param[out] code The exit code of the process. + * + * @returns A dds_retcode_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Process has terminated and its exit code has been captured. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * Process is still alive (only when timeout == 0). + * @retval DDS_RETCODE_TIMEOUT + * Process is still alive (even after the timeout). + * @retval DDS_RETCODE_BAD_PARAMETER + * Negative timeout. + * @retval DDS_RETCODE_NOT_FOUND + * Process unknown. + * @retval DDS_RETCODE_ERROR + * Getting the exit code failed for an unknown reason. + */ +DDS_EXPORT dds_retcode_t +ddsrt_proc_waitpid( + ddsrt_pid_t pid, + dds_duration_t timeout, + int32_t *code); + +/** + * @brief Wait for a random child process to have finished. + * + * This function will wait until anyone of the child processes has + * finished or the timeout is reached. + * + * If a process finished, then the exit code of that process will be + * copied into the given 'code' argument. The pid of the process will + * be put in the 'pid' argument. + * + * Internally, the timeout can be round-up to the nearest milliseconds or + * seconds, depending on the platform. + * + * See ddsrt_proc_waitpid() for waiting on a specific child process. + * + * @param[in] timemout Time within a process is expected to finish. + * @param[out] pid Process ID (PID) of the finished process. + * @param[out] code The exit code of the process. + * + * @returns A dds_retcode_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * A process has terminated. + * Its exit code and pid have been captured. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * All child processes are still alive (only when timeout == 0). + * @retval DDS_RETCODE_TIMEOUT + * All child processes are still alive (even after the timeout). + * @retval DDS_RETCODE_BAD_PARAMETER + * Negative timeout. + * @retval DDS_RETCODE_NOT_FOUND + * There are no processes to wait for. + * @retval DDS_RETCODE_ERROR + * Getting the exit code failed for an unknown reason. + */ +DDS_EXPORT dds_retcode_t +ddsrt_proc_waitpids( + dds_duration_t timeout, + ddsrt_pid_t *pid, + int32_t *code); + +/** + * @brief Checks if a process exists. + * + * @param[in] pid Process ID (PID) to check if it exists. + * + * @returns A dds_retcode_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The process exists. + * @retval DDS_RETCODE_NOT_FOUND + * The process does not exist. + * @retval DDS_RETCODE_ERROR + * Determining if a process exists or not, failed. + */ +DDS_EXPORT dds_retcode_t +ddsrt_proc_exists( + ddsrt_pid_t pid); + +/** + * @brief Kill a process. + * + * This function will try to forcefully kill the process (identified + * by pid). + * + * When DDS_RETCODE_OK is returned, it doesn't mean that the process + * was actually killed. It only indicates that the process was + * 'told' to terminate. Call ddsrt_proc_exists() to know + * for sure if the process was killed. + * + * @param[in] pid Process ID (PID) of the process to terminate. + * + * @returns A dds_retcode_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Kill attempt has been started. + * @retval DDS_RETCODE_BAD_PARAMETER + * Process unknown. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * Caller is not allowed to kill the process. + * @retval DDS_RETCODE_ERROR + * Kill failed for an unknown reason. + */ +DDS_EXPORT dds_retcode_t +ddsrt_proc_kill( + ddsrt_pid_t pid); + + #if defined (__cplusplus) } #endif diff --git a/src/ddsrt/src/process.c b/src/ddsrt/src/process.c deleted file mode 100644 index 4aed282..0000000 --- a/src/ddsrt/src/process.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License - * v. 1.0 which is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause - */ -#include "dds/ddsrt/process.h" - -#if !defined(_WIN32) -# include -#endif - -ddsrt_pid_t -ddsrt_getpid(void) -{ -#if defined(_WIN32) - return GetCurrentProcessId(); -#else - /* Mapped to taskIdSelf() in VxWorks kernel mode. */ - return getpid(); -#endif -} diff --git a/src/ddsrt/src/process/posix/process.c b/src/ddsrt/src/process/posix/process.c new file mode 100644 index 0000000..f6ed5db --- /dev/null +++ b/src/ddsrt/src/process/posix/process.c @@ -0,0 +1,303 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dds/ddsrt/process.h" +#include "dds/ddsrt/string.h" +#include "dds/ddsrt/heap.h" + + +ddsrt_pid_t +ddsrt_getpid(void) +{ + /* Mapped to taskIdSelf() in VxWorks kernel mode. */ + return getpid(); +} + + +/* + * This'll take a argv and prefixes it with the given prefix. + * If argv is NULL, the new argv array will only contain the prefix and a NULL. + * The result array is always terminated with NULL. + */ +static char** +prefix_argv(const char *prefix, char *const argv_in[]) +{ + char **argv_out; + size_t argc = 0; + + assert(prefix); + + if (argv_in != NULL) { + while (argv_in[argc] != NULL) { + argc++; + } + } + + argv_out = ddsrt_calloc((argc + 2), sizeof(char*)); + if (argv_out) { + size_t argi; + argv_out[0] = (char*)prefix; + for (argi = 0; argi < argc; argi++) { + argv_out[argi + 1] = (char*)argv_in[argi]; + } + argv_out[argc + 1] = NULL; + } + + return argv_out; +} + + +static void no_op(int sig) +{ + (void)sig; +} + + +static dds_retcode_t +waitpids( + ddsrt_pid_t request_pid, + dds_duration_t timeout, + ddsrt_pid_t *child_pid, + int32_t *code) +{ + struct sigaction sigactold; + struct sigaction sigact; + dds_retcode_t rv; + int options = 0; + int ret; + int s; + + if (timeout < 0) { + return DDS_RETCODE_BAD_PARAMETER; + } + + if (timeout == 0) { + options = WNOHANG; + } else if (timeout != DDS_INFINITY) { + /* Round-up timemout to alarm seconds. */ + unsigned secs; + secs = (unsigned)(timeout / DDS_NSECS_IN_SEC); + if ((timeout % DDS_NSECS_IN_SEC) != 0) { + secs++; + } + /* Be sure that the SIGALRM only wakes up waitpid. */ + sigemptyset (&sigact.sa_mask); + sigact.sa_handler = no_op; + sigact.sa_flags = 0; + sigaction (SIGALRM, &sigact, &sigactold); + /* Now, set the alarm. */ + alarm(secs); + } + + ret = waitpid(request_pid, &s, options); + if (ret > 0) { + if (code) { + if (WIFEXITED(s)) { + *code = WEXITSTATUS(s); + } else { + *code = 1; + } + } + if (child_pid) { + *child_pid = ret; + } + rv = DDS_RETCODE_OK; + } else if (ret == 0) { + /* Process is still alive. */ + rv = DDS_RETCODE_PRECONDITION_NOT_MET; + } else if ((ret == -1) && (errno == EINTR)) { + /* Interrupted, + * so process(es) likely didn't change state and are/is alive. */ + rv = DDS_RETCODE_TIMEOUT; + } else if ((ret == -1) && (errno == ECHILD)) { + /* Unknown pid. */ + rv = DDS_RETCODE_NOT_FOUND; + } else { + /* Unknown error. */ + rv = DDS_RETCODE_ERROR; + } + + if ((timeout != 0) && (timeout != DDS_INFINITY)) { + /* Clean the alarm. */ + alarm(0); + /* Reset SIGALRM actions. */ + sigaction(SIGALRM, &sigactold, NULL); + } + + return rv; +} + + + +dds_retcode_t +ddsrt_proc_create( + const char *executable, + char *const argv[], + ddsrt_pid_t *pid) +{ + dds_retcode_t rv; + char **exec_argv; + int exec_fds[2]; + int exec_err; + pid_t spawn; + ssize_t nr; + + assert(executable != NULL); + assert(pid != NULL); + + /* Prefix the argv with the executable, which is the convention. */ + exec_argv = prefix_argv(executable, argv); + if (exec_argv == NULL) { + return DDS_RETCODE_OUT_OF_RESOURCES; + } + + /* Prepare pipe to know the result of the exec. */ + if (pipe(exec_fds) == -1) { + rv = DDS_RETCODE_OUT_OF_RESOURCES; + goto fail_pipe; + } + if ((fcntl(exec_fds[0], F_SETFD, fcntl(exec_fds[0], F_GETFD) | FD_CLOEXEC) == -1) || + (fcntl(exec_fds[1], F_SETFD, fcntl(exec_fds[1], F_GETFD) | FD_CLOEXEC) == -1) ){ + rv = DDS_RETCODE_ERROR; + goto fail_fctl; + } + + /* Create a new process. */ + spawn = fork(); + if (spawn == -1) + { + rv = DDS_RETCODE_ERROR; + goto fail_fork; + } + else if (spawn == 0) + { + /* Child process */ + + /* Run the executable, replacing current process. */ + execv(executable, exec_argv); + + /* If executing this, something has gone wrong */ + exec_err = errno; + (void)write(exec_fds[1], &exec_err, sizeof(int)); + close(exec_fds[1]); + close(exec_fds[0]); + ddsrt_free(exec_argv); + _exit(1); + } + else + { + /* Parent process */ + + /* Get execv result. */ + rv = DDS_RETCODE_ERROR; + close(exec_fds[1]); + nr = read(exec_fds[0], &exec_err, sizeof(int)); + if (nr == 0) { + /* Pipe closed by successful execv. */ + rv = DDS_RETCODE_OK; + } else if (nr == sizeof(int)) { + /* Translate execv error. */ + if ((exec_err == ENOENT ) || + (exec_err == ENOEXEC) ){ + rv = DDS_RETCODE_BAD_PARAMETER; + } else if (exec_err == EACCES) { + rv = DDS_RETCODE_NOT_ALLOWED; + } + } + close(exec_fds[0]); + + if (rv == DDS_RETCODE_OK) { + /* Remember child pid. */ + *pid = spawn; + } else { + /* Remove the failed fork pid from the system list. */ + waitpid(spawn, NULL, 0); + } + } + + ddsrt_free(exec_argv); + return rv; + +fail_fork: +fail_fctl: + close(exec_fds[0]); + close(exec_fds[1]); +fail_pipe: + ddsrt_free(exec_argv); + return rv; +} + + + +dds_retcode_t +ddsrt_proc_waitpid( + ddsrt_pid_t pid, + dds_duration_t timeout, + int32_t *code) +{ + if (pid > 0) { + return waitpids(pid, timeout, NULL, code); + } + return DDS_RETCODE_BAD_PARAMETER; +} + + + +dds_retcode_t +ddsrt_proc_waitpids( + dds_duration_t timeout, + ddsrt_pid_t *pid, + int32_t *code) +{ + return waitpids(0, timeout, pid, code); +} + + + +dds_retcode_t +ddsrt_proc_exists( + ddsrt_pid_t pid) +{ + if (kill(pid, 0) == 0) + return DDS_RETCODE_OK; + else if (errno == EPERM) + return DDS_RETCODE_OK; + else if (errno == ESRCH) + return DDS_RETCODE_NOT_FOUND; + else + return DDS_RETCODE_ERROR; +} + + + +dds_retcode_t +ddsrt_proc_kill( + ddsrt_pid_t pid) +{ + if (kill(pid, SIGKILL) == 0) + return DDS_RETCODE_OK; + else if (errno == EPERM) + return DDS_RETCODE_ILLEGAL_OPERATION; + else if (errno == ESRCH) + return DDS_RETCODE_BAD_PARAMETER; + else + return DDS_RETCODE_ERROR; +} diff --git a/src/ddsrt/src/process/windows/process.c b/src/ddsrt/src/process/windows/process.c new file mode 100644 index 0000000..91cebec --- /dev/null +++ b/src/ddsrt/src/process/windows/process.c @@ -0,0 +1,447 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include "dds/ddsrt/heap.h" +#include "dds/ddsrt/time.h" +#include "dds/ddsrt/string.h" +#include "dds/ddsrt/atomics.h" +#include "dds/ddsrt/process.h" +#include "dds/ddsrt/timeconv.h" + + +ddsrt_pid_t +ddsrt_getpid(void) +{ + return GetCurrentProcessId(); +} + + + +static HANDLE pid_to_phdl (ddsrt_pid_t pid); +static dds_retcode_t process_get_exit_code(HANDLE phdl, int32_t *code); +static dds_retcode_t process_kill (HANDLE phdl); +static char* commandline (const char *exe, char *const argv_in[]); +static BOOL child_add (HANDLE phdl); +static void child_remove (HANDLE phdl); +static DWORD child_list (HANDLE *list, DWORD max); +static HANDLE child_handle (ddsrt_pid_t pid); + + + +dds_retcode_t +ddsrt_proc_create( + const char *executable, + char *const argv[], + ddsrt_pid_t *pid) +{ + dds_retcode_t rv = DDS_RETCODE_ERROR; + PROCESS_INFORMATION process_info; + STARTUPINFO si; + char *cmd; + LPTCH environment; + + assert(executable != NULL); + assert(pid != NULL); + + cmd = commandline(executable, argv); + if (cmd == NULL) { + return DDS_RETCODE_OUT_OF_RESOURCES; + } + + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + /* The new process will inherit the input/output handles. */ + /* TODO: Redirect is not working yet. */ + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + /* Get the environment variables to pass along. */ + environment = GetEnvironmentStrings(); + if(environment){ + BOOL created; + created = CreateProcess(executable, // ApplicationName + cmd, // CommandLine + NULL, // ProcessAttributes + NULL, // ThreadAttributes + TRUE, // InheritHandles + CREATE_NO_WINDOW, // dwCreationFlags + (LPVOID)environment, // Environment + NULL, // CurrentDirectory + &si, // StartupInfo + &process_info); // ProcessInformation + if (created) { + if (child_add(process_info.hProcess)) { + *pid = process_info.dwProcessId; + rv = DDS_RETCODE_OK; + } else { + process_kill(process_info.hProcess); + rv = DDS_RETCODE_OUT_OF_RESOURCES; + } + } else { + DWORD error = GetLastError(); + if ((ERROR_FILE_NOT_FOUND == error) || + (ERROR_PATH_NOT_FOUND == error)) { + rv = DDS_RETCODE_BAD_PARAMETER; + } else if (ERROR_ACCESS_DENIED == error) { + rv = DDS_RETCODE_NOT_ALLOWED; + } + } + FreeEnvironmentStrings(environment); + } + + ddsrt_free(cmd); + + return rv; +} + + + +dds_retcode_t +ddsrt_proc_waitpid( + ddsrt_pid_t pid, + dds_duration_t timeout, + int32_t *code) +{ + dds_retcode_t rv = DDS_RETCODE_OK; + HANDLE phdl; + DWORD ret; + + if (timeout < 0) { + return DDS_RETCODE_BAD_PARAMETER; + } + + phdl = child_handle(pid); + if (phdl == 0) { + return DDS_RETCODE_NOT_FOUND; + } + + if (timeout > 0) { + ret = WaitForSingleObject(phdl, ddsrt_duration_to_msecs_ceil(timeout)); + if (ret != WAIT_OBJECT_0) { + if (ret == WAIT_TIMEOUT) { + rv = DDS_RETCODE_TIMEOUT; + } else { + rv = DDS_RETCODE_ERROR; + } + } + } + + if (rv == DDS_RETCODE_OK) { + rv = process_get_exit_code(phdl, code); + } + + if (rv == DDS_RETCODE_OK) { + child_remove(phdl); + } + + + return rv; +} + + + +dds_retcode_t +ddsrt_proc_waitpids( + dds_duration_t timeout, + ddsrt_pid_t *pid, + int32_t *code) +{ + dds_retcode_t rv = DDS_RETCODE_OK; + HANDLE hdls[MAXIMUM_WAIT_OBJECTS]; + HANDLE phdl; + DWORD cnt; + DWORD ret; + + if (timeout < 0) { + return DDS_RETCODE_BAD_PARAMETER; + } + + cnt = child_list(hdls, MAXIMUM_WAIT_OBJECTS); + if (cnt == 0) { + return DDS_RETCODE_NOT_FOUND; + } + + ret = WaitForMultipleObjects(cnt, hdls, FALSE, ddsrt_duration_to_msecs_ceil(timeout)); + if ((ret < WAIT_OBJECT_0) || (ret >= (WAIT_OBJECT_0 + cnt))) { + if (ret == WAIT_TIMEOUT) { + if (timeout == 0) { + rv = DDS_RETCODE_PRECONDITION_NOT_MET; + } else { + rv = DDS_RETCODE_TIMEOUT; + } + } else { + rv = DDS_RETCODE_ERROR; + } + } else { + /* Get the handle of the specific child that was triggered. */ + phdl = hdls[ret - WAIT_OBJECT_0]; + } + + if (rv == DDS_RETCODE_OK) { + rv = process_get_exit_code(phdl, code); + } + + if (rv == DDS_RETCODE_OK) { + if (pid) { + *pid = GetProcessId(phdl); + } + child_remove(phdl); + } + + return rv; +} + + + +dds_retcode_t +ddsrt_proc_exists( + ddsrt_pid_t pid) +{ + dds_retcode_t rv = DDS_RETCODE_NOT_FOUND; + HANDLE phdl; + + phdl = pid_to_phdl(pid); + if (phdl != 0) { + rv = process_get_exit_code(phdl, NULL); + if (rv == DDS_RETCODE_PRECONDITION_NOT_MET) { + /* Process still exists. */ + rv = DDS_RETCODE_OK; + } else if (rv == DDS_RETCODE_OK) { + /* The process has gone. */ + rv = DDS_RETCODE_NOT_FOUND; + } else { + rv = DDS_RETCODE_ERROR; + } + CloseHandle(phdl); + } + + return rv; +} + + + +dds_retcode_t +ddsrt_proc_kill( + ddsrt_pid_t pid) +{ + dds_retcode_t rv = DDS_RETCODE_BAD_PARAMETER; + HANDLE phdl; + + phdl = pid_to_phdl(pid); + if (phdl != 0) { + /* Forcefully kill. */ + rv = process_kill(phdl); + CloseHandle(phdl); + } + + return rv; +} + + + +static HANDLE +pid_to_phdl(ddsrt_pid_t pid) +{ + return OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE, FALSE, pid); +} + + + +static dds_retcode_t +process_get_exit_code( + HANDLE phdl, + int32_t *code) +{ + dds_retcode_t rv = DDS_RETCODE_ERROR; + DWORD tr; + + assert(phdl != 0); + + if (GetExitCodeProcess(phdl, &tr)) { + if (tr == STILL_ACTIVE) { + rv = DDS_RETCODE_PRECONDITION_NOT_MET; + } else { + if (code) { + *code = (int32_t)tr; + } + rv = DDS_RETCODE_OK; + } + } + + return rv; +} + + + +/* Forcefully kill the given process. */ +static dds_retcode_t +process_kill(HANDLE phdl) +{ + assert(phdl != 0); + if (TerminateProcess(phdl, 1 /* exit code */) != 0) { + return DDS_RETCODE_OK; + } + return DDS_RETCODE_ERROR; +} + + + +/* Add quotes to a given string, escape it and add it to a buffer. */ +static char* +insert_char(char *buf, size_t *len, size_t *max, char c) +{ + static const size_t buf_inc = 64; + if (*len == *max) { + *max += buf_inc; + buf = ddsrt_realloc(buf, *max); + } + if (buf) { + buf[(*len)++] = c; + } + return buf; +} +static char* +stringify_cat(char *buf, size_t *len, size_t *max, const char *str) +{ + char last = '\0'; + + /* Start stringification with an opening double-quote. */ + buf = insert_char(buf, len, max, '\"'); + if (!buf) goto end; + + /* Copy and escape the string. */ + while ((*str) != '\0') { + if (*str == '\"') { + buf = insert_char(buf, len, max, '\\'); + if (!buf) goto end; + } + buf = insert_char(buf, len, max, *str); + if (!buf) goto end; + last = *str; + str++; + } + + /* For some reason, only the last backslash will be stripped. + * No need to escape the other backslashes. */ + if (last == '\\') { + buf = insert_char(buf, len, max, '\\'); + if (!buf) goto end; + } + + /* End stringification. */ + buf = insert_char(buf, len, max, '\"'); + if (!buf) goto end; + buf = insert_char(buf, len, max, ' '); + +end: + return buf; +} + + + +/* Create command line with executable and arguments. */ +static char* +commandline(const char *exe, char *const argv_in[]) +{ + char *cmd = NULL; + size_t len = 0; + size_t max = 0; + size_t argi; + + assert(exe); + + /* Add quoted and escaped executable. */ + cmd = stringify_cat(cmd, &len, &max, exe); + if (!cmd) goto end; + + /* Add quoted and escaped arguments. */ + if (argv_in != NULL) { + for (argi = 0; argv_in[argi] != NULL; argi++) { + cmd = stringify_cat(cmd, &len, &max, argv_in[argi]); + if (!cmd) goto end; + } + } + + /* Terminate command line string. */ + cmd = insert_char(cmd, &len, &max, '\0'); + +end: + return cmd; +} + + +/* Maintain a list of children to be able to wait for all them. */ +static ddsrt_atomic_voidp_t g_children[MAXIMUM_WAIT_OBJECTS] = {0}; + +static BOOL +child_update(HANDLE old, HANDLE new) +{ + BOOL updated = FALSE; + for (int i = 0; (i < MAXIMUM_WAIT_OBJECTS) && (!updated); i++) + { + updated = ddsrt_atomic_casvoidp(&(g_children[i]), old, new); + } + return updated; +} + +static BOOL +child_add(HANDLE phdl) +{ + return child_update(0, phdl); +} + +static void +child_remove(HANDLE phdl) +{ + (void)child_update(phdl, 0); +} + +static DWORD +child_list(HANDLE *list, DWORD max) +{ + HANDLE hdl; + int cnt = 0; + assert(list); + assert(max <= MAXIMUM_WAIT_OBJECTS); + for (int i = 0; (i < MAXIMUM_WAIT_OBJECTS); i++) + { + hdl = ddsrt_atomic_ldvoidp(&(g_children[i])); + if (hdl != 0) { + list[cnt++] = hdl; + } + } + return cnt; +} + +static HANDLE +child_handle(ddsrt_pid_t pid) +{ + HANDLE phdl = 0; + + for (int i = 0; (i < MAXIMUM_WAIT_OBJECTS) && (phdl == 0); i++) + { + phdl = ddsrt_atomic_ldvoidp(&(g_children[i])); + if (phdl != 0) { + if (GetProcessId(phdl) != pid) { + phdl = 0; + } + } + } + + return phdl; +} diff --git a/src/ddsrt/tests/CMakeLists.txt b/src/ddsrt/tests/CMakeLists.txt index 552cfd2..02c92ef 100644 --- a/src/ddsrt/tests/CMakeLists.txt +++ b/src/ddsrt/tests/CMakeLists.txt @@ -25,6 +25,7 @@ set(sources "random.c" "strlcpy.c" "socket.c" + "process.c" "select.c") add_cunit_executable(cunit_ddsrt ${sources}) @@ -42,3 +43,26 @@ endif() target_include_directories( cunit_ddsrt PRIVATE "$") + +# Create a separate test application that will be used to +# test process management. +add_executable(process_app process_app.c) +target_link_libraries(process_app PRIVATE ddsrt) +target_include_directories( + process_app + PRIVATE + "$") +# Force the app to be at the same location, no matter what platform or build type. +set_target_properties( + process_app + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_CURRENT_BINARY_DIR} + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_CURRENT_BINARY_DIR} ) +# Let the cunit application know the location and name of the test application. +set(process_app_name "${CMAKE_CURRENT_BINARY_DIR}/process_app${CMAKE_EXECUTABLE_SUFFIX}") +configure_file( + "process_test.h.in" "${CMAKE_CURRENT_BINARY_DIR}/include/process_test.h" @ONLY) + diff --git a/src/ddsrt/tests/process.c b/src/ddsrt/tests/process.c new file mode 100644 index 0000000..c000cf7 --- /dev/null +++ b/src/ddsrt/tests/process.c @@ -0,0 +1,284 @@ +/* + * Copyright(c) 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include "CUnit/Test.h" +#include "process_test.h" +#include "dds/ddsrt/time.h" +#include "dds/ddsrt/strtol.h" +#include "dds/ddsrt/environ.h" +#include "dds/ddsrt/process.h" + + +/* + * Create a process that is expected to exit quickly. + * Compare the exit code with the expected exit code. + */ +static void create_and_test_exit(const char *arg, int code) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + int32_t status; + char *argv[] = { NULL, NULL }; + + argv[0] = (char*)arg; + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + + ret = ddsrt_proc_waitpid(pid, DDS_SECS(10), &status); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + + /* Check result. */ + CU_ASSERT_EQUAL(status, code); + + /* Garbage collection when needed. */ + if (ret != DDS_RETCODE_OK) { + ddsrt_proc_kill(pid); + } +} + + +/* + * Try to create a process without arguments. + * The exit status of the process should be PROCESS_DONE_NOTHING_EXIT_CODE. + */ +CU_Test(ddsrt_process, create) +{ + create_and_test_exit(NULL, TEST_CREATE_EXIT); +} + + +/* + * Create a process that'll sleep for a while. + * Try to kill that process. + */ +CU_Test(ddsrt_process, kill) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + + /* Sleep for 20 seconds. It should be killed before then. */ + char *argv[] = { TEST_SLEEP_ARG, "20", NULL }; + + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + CU_ASSERT_NOT_EQUAL_FATAL(pid, 0); + + /* Check if process is running. */ + ret = ddsrt_proc_exists(pid); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + + /* Kill it. */ + ret = ddsrt_proc_kill(pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + + /* Check if process is actually gone. */ + ret = ddsrt_proc_waitpid(pid, DDS_SECS(10), NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + ret = ddsrt_proc_exists(pid); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_NOT_FOUND); +} + + +/* + * Create a process that'll return it's own pid value (reduced + * to fit the exit code range). It should match the pid that was + * returned by the process create (also reduced to be able to + * match the returned semi-pid value). + */ +CU_Test(ddsrt_process, pid) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + int32_t status; + char *argv[] = { TEST_PID_ARG, NULL }; + + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + CU_ASSERT_NOT_EQUAL_FATAL(pid, 0); + + ret = ddsrt_proc_waitpid(pid, DDS_SECS(10), &status); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + + /* Compare the pid values. */ + CU_ASSERT_EQUAL(status, TEST_PID_EXIT(pid)); + + /* Garbage collection when needed. */ + if (ret != DDS_RETCODE_OK) { + ddsrt_proc_kill(pid); + } +} + + +/* + * Set a environment variable in the parent process. + * Create a process that should have access to that env var. + */ +CU_Test(ddsrt_process, env) +{ + dds_retcode_t ret; + + ret = ddsrt_setenv(TEST_ENV_VAR_NAME, TEST_ENV_VAR_VALUE); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + + create_and_test_exit(TEST_ENV_ARG, TEST_ENV_EXIT); +} + + +/* + * Try to create a process with an non-existing executable file. + * It should fail. + */ +CU_Test(ddsrt_process, invalid) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + + ret = ddsrt_proc_create("ProbablyNotAnValidExecutable", NULL, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_BAD_PARAMETER); + + /* Garbage collection when needed. */ + if (ret == DDS_RETCODE_OK) { + ddsrt_proc_kill(pid); + } +} + + +/* + * Create a process with a backslash in the argument + */ +CU_Test(ddsrt_process, arg_bslash) +{ + create_and_test_exit(TEST_BSLASH_ARG, TEST_BSLASH_EXIT); +} + + +/* + * Create a process with a double-quote in the argument + */ +CU_Test(ddsrt_process, arg_dquote) +{ + create_and_test_exit(TEST_DQUOTE_ARG, TEST_DQUOTE_EXIT); +} + + +/* + * Create two processes and wait for them simultaneously. + */ +CU_Test(ddsrt_process, waitpids) +{ + dds_retcode_t ret; + ddsrt_pid_t child; + ddsrt_pid_t pid1 = 0; + ddsrt_pid_t pid2 = 0; + int32_t status; + + /* Use retpid option to identify return values. */ + char *argv[] = { TEST_PID_ARG, NULL }; + + /* Start two processes. */ + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid1); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid2); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + + /* Wait for both processes to have finished. */ + while (((pid1 != 0) || (pid2 != 0)) && (ret == DDS_RETCODE_OK)) { + ret = ddsrt_proc_waitpids(DDS_SECS(10), &child, &status); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); + if (child == pid1) { + CU_ASSERT_EQUAL(status, TEST_PID_EXIT(pid1)); + pid1 = 0; + } else if (child == pid2) { + CU_ASSERT_EQUAL(status, TEST_PID_EXIT(pid2)); + pid2 = 0; + } else { + CU_ASSERT(0); + } + } + + ret = ddsrt_proc_waitpids(DDS_SECS(10), &child, &status); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_NOT_FOUND); + + /* Garbage collection when needed. */ + if (pid1 != 0) { + ddsrt_proc_kill(pid1); + } + if (pid2 != 0) { + ddsrt_proc_kill(pid2); + } +} + + +/* + * Create a sleeping process. Waiting for it should timeout. + */ +CU_Test(ddsrt_process, waitpid_timeout) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + + /* Sleep for 20 seconds. We should have a timeout before then. */ + char *argv[] = { TEST_SLEEP_ARG, "20", NULL }; + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + CU_ASSERT_NOT_EQUAL_FATAL(pid, 0); + + /* Timeout 0 should return DDS_RETCODE_PRECONDITION_NOT_MET when alive. */ + ret = ddsrt_proc_waitpid(pid, 0, NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_PRECONDITION_NOT_MET); + + /* Valid timeout should return DDS_RETCODE_TIMEOUT when alive. */ + ret = ddsrt_proc_waitpid(pid, DDS_SECS(1), NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_TIMEOUT); + + /* Kill it. */ + ret = ddsrt_proc_kill(pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + + /* When killed, DDS_RETCODE_OK should be returned. */ + ret = ddsrt_proc_waitpid(pid, DDS_SECS(10), NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); +} + + +/* + * Create a sleeping process. Waiting for it should timeout. + */ +CU_Test(ddsrt_process, waitpids_timeout) +{ + dds_retcode_t ret; + ddsrt_pid_t pid; + + /* Sleep for 20 seconds. We should have a timeout before then. */ + char *argv[] = { TEST_SLEEP_ARG, "20", NULL }; + ret = ddsrt_proc_create(TEST_APPLICATION, argv, &pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + CU_ASSERT_NOT_EQUAL_FATAL(pid, 0); + + /* Timeout 0 should return DDS_RETCODE_PRECONDITION_NOT_MET when alive. */ + ret = ddsrt_proc_waitpids(0, NULL, NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_PRECONDITION_NOT_MET); + + /* Valid timeout should return DDS_RETCODE_TIMEOUT when alive. */ + ret = ddsrt_proc_waitpids(DDS_SECS(1), NULL, NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_TIMEOUT); + + /* Kill it. */ + ret = ddsrt_proc_kill(pid); + CU_ASSERT_EQUAL_FATAL(ret, DDS_RETCODE_OK); + + /* When killed, DDS_RETCODE_OK should be returned. */ + ret = ddsrt_proc_waitpids(DDS_SECS(10), NULL, NULL); + CU_ASSERT_EQUAL(ret, DDS_RETCODE_OK); +} diff --git a/src/ddsrt/tests/process_app.c b/src/ddsrt/tests/process_app.c new file mode 100644 index 0000000..a0c402a --- /dev/null +++ b/src/ddsrt/tests/process_app.c @@ -0,0 +1,108 @@ +/* + * Copyright(c) 2019 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include "process_test.h" +#include "dds/ddsrt/strtol.h" +#include "dds/ddsrt/environ.h" +#include "dds/ddsrt/process.h" + + +static int test_create(void) +{ + printf(" Process: created without args.\n"); + return TEST_CREATE_EXIT; +} + +static int test_sleep(int argi, int argc, char **argv) +{ + argi++; + if (argi < argc) { + long long dorment; + ddsrt_strtoll(argv[argi], NULL, 0, &dorment); + printf(" Process: sleep %d seconds.\n", (int)dorment); + dds_sleepfor(DDS_SECS((int64_t)dorment)); + } else { + printf(" Process: no --sleep value.\n"); + return TEST_EXIT_WRONG_ARGS; + } + /* Expected to be destroyed before reaching this. */ + return TEST_EXIT_FAILURE; +} + +static int test_pid(void) +{ + int ret; + ddsrt_pid_t pid; + pid = ddsrt_getpid(); + ret = TEST_PID_EXIT(pid); + printf(" Process: pid %d reduced to %d exit code.\n", (int)pid, ret); + return ret; +} + +static int test_env(void) +{ + int ret = TEST_EXIT_FAILURE; + char *envptr = NULL; + if (ddsrt_getenv(TEST_ENV_VAR_NAME, &envptr) == DDS_RETCODE_OK) { + printf(" Process: env %s=%s.\n", TEST_ENV_VAR_NAME, envptr); + if (strcmp(envptr, TEST_ENV_VAR_VALUE) == 0) { + ret = TEST_ENV_EXIT; + } + } else { + printf(" Process: failed to get environment variable.\n"); + } + return ret; +} + +static int test_bslash(void) +{ + printf(" Process: backslash argument.\n"); + return TEST_BSLASH_EXIT; +} + +static int test_dquote(void) +{ + printf(" Process: double-quote argument.\n"); + return TEST_DQUOTE_EXIT; +} + + +/* + * The spawned application used in the process tests. + */ +int main(int argc, char **argv) +{ + int ret; + + if (argc == 1) { + ret = test_create(); + } else { + if (strcmp(argv[1], TEST_SLEEP_ARG) == 0) { + ret = test_sleep(1, argc, argv); + } else if (strcmp(argv[1], TEST_PID_ARG) == 0) { + ret = test_pid(); + } else if (strcmp(argv[1], TEST_ENV_ARG) == 0) { + ret = test_env(); + } else if (strcmp(argv[1], TEST_BSLASH_ARG) == 0) { + ret = test_bslash(); + } else if (strcmp(argv[1], TEST_DQUOTE_ARG) == 0) { + ret = test_dquote(); + } else { + printf(" Process: unknown argument.\n"); + ret = TEST_EXIT_WRONG_ARGS; + } + } + + return ret; +} diff --git a/src/ddsrt/tests/process_test.h.in b/src/ddsrt/tests/process_test.h.in new file mode 100644 index 0000000..25cd632 --- /dev/null +++ b/src/ddsrt/tests/process_test.h.in @@ -0,0 +1,42 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDSRT_TEST_PROCESS_TEST_H +#define DDSRT_TEST_PROCESS_TEST_H + +/* Get the application name from cmake to automatically + * get the proper extension and location. */ +#define TEST_APPLICATION "@process_app_name@" + +#define TEST_SLEEP_ARG "--sleep" + +#define TEST_EXIT_GENERIC_OK (0) +#define TEST_EXIT_FAILURE (1) +#define TEST_EXIT_WRONG_ARGS (2) + +#define TEST_CREATE_ARG NULL +#define TEST_CREATE_EXIT (0) + +#define TEST_PID_ARG "--retpid" +#define TEST_PID_EXIT(pid) ((int)(int32_t)(pid % 127)) + +#define TEST_ENV_ARG "--checkenv" +#define TEST_ENV_EXIT (12) +#define TEST_ENV_VAR_NAME "TEST_ENV_VAR_NAME" +#define TEST_ENV_VAR_VALUE "TEST_ENV_VAR_VALUE" + +#define TEST_BSLASH_ARG "\\left\\\\right\\" +#define TEST_BSLASH_EXIT (int)('\\') + +#define TEST_DQUOTE_ARG "\"left\"\"right\"" +#define TEST_DQUOTE_EXIT (int)('"') + +#endif /* DDSRT_TEST_PROCESS_TEST_H */