Rudimentary process management.

Signed-off-by: Martin Bremmer <martin.bremmer@adlinktech.com>
This commit is contained in:
Martin Bremmer 2019-04-05 15:00:48 +02:00 committed by eboasson
parent 386d5d3029
commit 5a8197fa2b
9 changed files with 1378 additions and 33 deletions

View file

@ -74,7 +74,6 @@ list(APPEND sources
"${source_path}/io.c" "${source_path}/io.c"
"${source_path}/log.c" "${source_path}/log.c"
"${source_path}/retcode.c" "${source_path}/retcode.c"
"${source_path}/process.c"
"${source_path}/strtod.c" "${source_path}/strtod.c"
"${source_path}/strtol.c") "${source_path}/strtol.c")
@ -104,7 +103,7 @@ list(APPEND sources
# network stack. In order to mix-and-match various compilers, architectures, # network stack. In order to mix-and-match various compilers, architectures,
# operating systems, etc input from the build system is required. # operating systems, etc input from the build system is required.
foreach(feature atomics cdtors environ heap ifaddrs random rusage 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") if(EXISTS "${include_path}/dds/ddsrt/${feature}.h")
list(APPEND headers "${include_path}/dds/ddsrt/${feature}.h") list(APPEND headers "${include_path}/dds/ddsrt/${feature}.h")
file(GLOB file(GLOB

View file

@ -13,11 +13,9 @@
#define DDSRT_PROCESS_H #define DDSRT_PROCESS_H
#include "dds/export.h" #include "dds/export.h"
#include "dds/ddsrt/time.h"
#include "dds/ddsrt/types.h" #include "dds/ddsrt/types.h"
#include "dds/ddsrt/retcode.h"
#if defined (__cplusplus)
extern "C" {
#endif
#if defined(_WIN32) #if defined(_WIN32)
typedef DWORD ddsrt_pid_t; typedef DWORD ddsrt_pid_t;
@ -33,6 +31,11 @@ typedef pid_t ddsrt_pid_t;
#endif #endif
#endif /* _WIN32 */ #endif /* _WIN32 */
#if defined (__cplusplus)
extern "C" {
#endif
/** /**
* @brief Return process ID (PID) of the calling process. * @brief Return process ID (PID) of the calling process.
* *
@ -41,6 +44,168 @@ typedef pid_t ddsrt_pid_t;
DDS_EXPORT ddsrt_pid_t DDS_EXPORT ddsrt_pid_t
ddsrt_getpid(void); 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) #if defined (__cplusplus)
} }
#endif #endif

View file

@ -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 <unistd.h>
#endif
ddsrt_pid_t
ddsrt_getpid(void)
{
#if defined(_WIN32)
return GetCurrentProcessId();
#else
/* Mapped to taskIdSelf() in VxWorks kernel mode. */
return getpid();
#endif
}

View file

@ -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 <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#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;
}

View file

@ -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 <stdio.h>
#include <errno.h>
#include <assert.h>
#include <process.h>
#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;
}

View file

@ -25,6 +25,7 @@ set(sources
"random.c" "random.c"
"strlcpy.c" "strlcpy.c"
"socket.c" "socket.c"
"process.c"
"select.c") "select.c")
add_cunit_executable(cunit_ddsrt ${sources}) add_cunit_executable(cunit_ddsrt ${sources})
@ -42,3 +43,26 @@ endif()
target_include_directories( target_include_directories(
cunit_ddsrt PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>") cunit_ddsrt PRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# 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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# 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)

284
src/ddsrt/tests/process.c Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <assert.h>
#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);
}

View file

@ -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 <stdio.h>
#include <string.h>
#include <assert.h>
#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;
}

View file

@ -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 */