Attached is a new patch of process helpers. > -----Original Message----- > From: Martin Sebor [mailto:[EMAIL PROTECTED] > Sent: Wednesday, August 02, 2006 8:30 PM > To: [email protected] > Subject: Re: testsuite process helpers [PATCH] [...] > > > (rw_wait_pid): Added the timeout parameter. > > Excellent! Although I think the UNIX branch should probably > try to be more robust about handling an existing alarm (or > avoiding the function and using some other mechanism).
In this patch SIGCHLD signal was using. > > (rw_process_kill): New function to kill the specified process. > > FYI: sending a process the KILL signal is considered quite > drastic since the signal cannot be caught and so the process > cannot clean up after itself and release system resources. > That's why the default behavior of the POSIX kill utility > (i.e., when the -s option is not specified) is to send the > TERM signal. I think rw_process_kill should behave similarly. > > I would suggest to add a second argument like this > > rw_process_kill (rw_pid_t, int signo = -1); > > When (signo == -1) the function should provide the same > behavior as the analogous code in wait_for_child() here > (i.e., start by sending SIGHUP, then SIGINT, then SIGTERM, > and only as the last resort, SIGKILL): > http://svn.apache.org/repos/asf/incubator/stdcxx/trunk/util/exec.cpp > (The Windows branch of the code can ignore the argument > unless there is some similar functionality in Win32). Done. ChangeLog: * rw_process.h (rw_pid_t): The type long changed to _RWSTD_SSIZE_T. (rw_wait_pid): Added the timeout parameter. (rw_process_kill): New function to terminate the specified process. * process.cpp [_WIN32] (__rw_map_errno): New function to get errno value from WinAPI last error code (__rw_split_cmd): Moved to #ifndef _WIN32/#endif [_WIN32] (_rw_vprocess_create): Used CreateProcess instead of rw_process_create(char*, char* []) [_WIN32] (rw_process_create): Used rw_process_create(char*, ...) instead of spawnp * 0.process.cpp: New test exercising the rw_process_create(), rw_process_kill() and rw_waitpid() functions. Farid.
Index: include/rw_process.h =================================================================== --- include/rw_process.h (revision 465232) +++ include/rw_process.h (working copy) @@ -30,8 +30,12 @@ #include <testdefs.h> +typedef _RWSTD_SSIZE_T rw_pid_t; -typedef long rw_pid_t; +// if == 0 - report about errors using rw_error() (default) +// if != 0 - do not report about errors +_TEST_EXPORT extern int +rw_process_error_report_mode; _TEST_EXPORT int rw_system (const char*, ...); @@ -45,15 +49,39 @@ // returns pid of the created process or -1 on error // (in which case errno is set to an appropriate value) _TEST_EXPORT rw_pid_t -rw_process_create (const char*, char* const []); +rw_process_create (const char* /*path*/, char* const /*argv*/[]); // result is a pointer to a buffer where the result code -// of the specified process will be stored, or NULL -// returns pid of the specified process or -1 on error -// (in which case errno is set to an appropriate value) -// Errors: +// of the specified process will be stored, or NULL +// +// the function suspends execution of the current process +// until a child has exited or specified timeout is reached +// +// returns: +// pid of the specified process if it has exited +// 0 when process still active +// -1 on error (in which case errno is set to an appropriate value) +// +// timeout is timeout interval in seconds. +// if timeout > 0 the function returns if the interval elapses +// if timeout == 0 the function returns immediately +// if timeout < 0 the function's time-out interval never elapses +// +// errors: // ECHILD: no specified process exists _TEST_EXPORT rw_pid_t -rw_waitpid (rw_pid_t, int*); +rw_waitpid (rw_pid_t /*pid*/, int* /*result*/, int /*timeout*/ = -1); +// returns: +// 0 when process terminated successfully +// 1 when signal was sent to the child process, but child process +// not terminated within 1 second interval +// -1 on error (in which case errno is set to an appropriate value) +// errors: +// ESRCH: the pid does not exist +// EPERM: the calling process does not have permission +// to terminate the specified process +_TEST_EXPORT int +rw_process_kill (rw_pid_t, int = -1); + #endif // RW_PROCESS_H_INCLUDED Index: self/0.process.cpp =================================================================== --- self/0.process.cpp (revision 0) +++ self/0.process.cpp (revision 0) @@ -0,0 +1,304 @@ +/************************************************************************ +* +* 0.process.cpp - test exercising the rw_process_create(), +* rw_process_kill() and rw_waitpid() functions +* +* $Id$ +* +************************************************************************ +* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed +* with this work for additional information regarding copyright +* ownership. The ASF licenses this file to you under the Apache +* License, Version 2.0 (the "License"); you may not use this file +* except in compliance with the License. You may obtain a copy of +* the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +* implied. See the License for the specific language governing +* permissions and limitations under the License. +* +**************************************************************************/ + +#include <string.h> // for strcmp() +#include <errno.h> // for errno + +#include <rw_process.h> // for rw_process_create(), rw_waitpid() +#include <driver.h> // for rw_test() + +static int _rw_child = 0; +static int _rw_timeout = 5; + +static char arg1[] = "--child=1"; +static char arg2[] = "--no-stdout"; + +static char* args [] = { 0, arg1, arg2, 0 }; +static const int nargs = sizeof (args) / sizeof (*args) - 1; + +static rw_pid_t _rw_pid = -1; + +static int join_test (rw_pid_t pid, bool should_hang) +{ + int result = 0; + const rw_pid_t ret = rw_waitpid (pid, &result, _rw_timeout); + + rw_assert (-1 != ret, __FILE__, __LINE__, + "rw_waitpid() failed, errno = %{#m}"); + + if (-1 == ret) + return 1; + + if (0 == ret) { + // mask rw_error diagnostic + rw_enable (rw_error, false); + + // time_out elapsed, kill the process + if (1 == rw_process_kill (pid)) { + // the process not yet terminated + // wait for process termination and remove the zombie process + rw_waitpid (pid, 0); + } + + // unmask rw_error diagnostic + rw_enable (rw_error); + + rw_assert (should_hang, __FILE__, __LINE__, + "The child process unexpectedly deadlocked"); + + return should_hang ? 0 : 1; + } + + rw_assert (!should_hang, __FILE__, __LINE__, + "Expected the deadlocked process, but process exited " + "with code: %d", + result); + + if (!should_hang) + rw_assert (0 == result, __FILE__, __LINE__, + "Process exit code: expected 0, got %d", result); + + return result; +} + +static int test_process_create1 () +{ + rw_info (0, 0, 0, + "Exercising the rw_process_create " + "(const char*, char* const []) overload"); + + const rw_pid_t pid = rw_process_create (args [0], args); + + rw_assert (-1 != pid, __FILE__, __LINE__, + "rw_process_create() failed, errno = %{#m}"); + + if (-1 == pid) + return 1; + + // save the pid for test exercising rw_waitpid() fail + if (-1 == _rw_pid) + _rw_pid = pid; + + return join_test (pid, false); +} + +static int test_process_create2 () +{ + rw_info (0, 0, 0, + "Exercising the rw_process_create (const char*, ...) overload"); + + const rw_pid_t pid = rw_process_create ("\"%s\" %s %s", + args[0], args[1], args[2]); + + rw_assert (-1 != pid, __FILE__, __LINE__, + "rw_process_create() failed, errno = %{#m}"); + + if (-1 == pid) + return 1; + + // save the pid for test exercising rw_waitpid() fail + if (-1 == _rw_pid) + _rw_pid = pid; + + return join_test (pid, false); +} + +static int test_process_deadlocked () +{ + rw_info (0, 0, 0, + "Exercising the rw_waitpid() with timeout and deadlocked process"); + + const rw_pid_t pid = rw_process_create ("\"%s\" --child=2 %s", + args[0], args[2]); + + rw_assert (-1 != pid, __FILE__, __LINE__, + "rw_process_create() failed, errno = %{#m}"); + + return -1 == pid ? 1 : join_test (pid, true); +} + +static int test_process_create_fail () +{ + rw_info (0, 0, 0, + "Exercising the rw_process_create() behavior " + "when invalid path specified"); + + // mask rw_error diagnostic + rw_enable (rw_error, false); + + const rw_pid_t pid = rw_process_create ("/\\/\\/\\", args); + + // unmask rw_error diagnostic + rw_enable (rw_error); + + rw_assert (-1 == pid, __FILE__, __LINE__, + "rw_process_create returns %ld, expected -1", + long (pid)); + + if (-1 != pid) { + rw_waitpid (pid, 0); + return 1; + } + + rw_assert (ENOENT == errno, __FILE__, __LINE__, + "errno: expected ENOENT, got %{#m}"); + + return ENOENT == errno ? 0 : 1; +} + +static int test_waitpid_fail () +{ + if (-1 == _rw_pid) { + rw_info (0, 0, 0, + "The test, exercising the rw_waitpid() behavior " + "when invalid pid specified is disabled"); + + return 0; + } + + rw_info (0, 0, 0, + "Exercising the rw_waitpid() behavior " + "when invalid pid specified"); + + // mask rw_error diagnostic + rw_enable (rw_error, false); + + const rw_pid_t pid = rw_waitpid (_rw_pid, 0); + + // unmask rw_error diagnostic + rw_enable (rw_error); + + rw_assert (-1 == pid, __FILE__, __LINE__, + "rw_waitpid returns %ld, expected -1", + long (pid)); + + if (-1 != pid) + return 1; + + rw_assert (ECHILD == errno, __FILE__, __LINE__, + "errno: expected ECHILD, got %{#m}"); + + return ECHILD == errno ? 0 : 1; +} + +static int test_process_kill_fail () +{ + if (-1 == _rw_pid) { + rw_info (0, 0, 0, + "The test, exercising the rw_process_kill() behavior " + "when invalid pid specified is disabled"); + + return 0; + } + + rw_info (0, 0, 0, + "Exercising the rw_process_kill() behavior " + "when invalid pid specified"); + + // mask rw_error diagnostic + rw_enable (rw_error, false); + + const int res = rw_process_kill (_rw_pid); + + // unmask rw_error diagnostic + rw_enable (rw_error); + + rw_assert (-1 == res, __FILE__, __LINE__, + "rw_process_kill returns %ld, expected -1", + long (res)); + + if (-1 != res) + return 1; + + rw_assert (ESRCH == errno, __FILE__, __LINE__, + "errno: expected ESRCH, got %{#m}"); + + return ESRCH == errno ? 0 : 1; +} + +static int +run_test (int argc, char** argv) +{ + if (_rw_child) { + + rw_info (0, 0, 0, + "The child process: _rw_child = %i", _rw_child); + + if (2 == _rw_child) { + // simulate the deadlock + while (true) ; + } + + // compare number of parameters with expected + if (nargs != argc) + return nargs; + + // compare the parameters with expected + for (int i = 1; i < argc; ++i) { + if (0 != strcmp (argv [i], args [i])) + return i; + } + + return 0; + } + + args [0] = argv [0]; + + int fails = 0; + + if (test_process_create1 ()) + ++fails; + + if (test_process_create2 ()) + ++fails; + + if (test_process_deadlocked ()) + ++fails; + + if (test_process_create_fail ()) + ++fails; + + if (test_waitpid_fail ()) + ++fails; + + if (test_process_kill_fail ()) + ++fails; + + return fails; +} + +int main (int argc, char *argv[]) +{ + return rw_test (argc, argv, __FILE__, + "0.process", + "", run_test, + "|-child#0 " + "|-timeout#", + &_rw_child, + &_rw_timeout, + 0 /*sentinel*/); +} Property changes on: self\0.process.cpp ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Index: src/process.cpp =================================================================== --- src/process.cpp (revision 465232) +++ src/process.cpp (working copy) @@ -29,7 +29,7 @@ #include <rw_process.h> -#include <stddef.h> +#include <stddef.h> // for size_t #include <stdarg.h> // for va_copy, va_list, ... #include <stdlib.h> // for free(), exit() #include <string.h> // for strchr() @@ -40,8 +40,9 @@ #include <driver.h> // for rw_note(), ... #include <rw_printf.h> // for rw_fprintf() -#ifndef ENOMEM -# define ENOMEM 12 // e.g., Linux, Solaris +#ifdef __CYGWIN__ +// use the Windows API on Cygwin +# define _WIN32 #endif /**************************************************************************/ @@ -51,16 +52,165 @@ /**************************************************************************/ -#if defined (_WIN32) || defined (_WIN64) -# include <process.h> // for spawnvp(), cwait() -#else +#ifdef _WIN32 + +# include <windows.h> // for WaitForSingleObject(), ... + +static int +_rw_map_errno (DWORD err) +{ + if (ERROR_WRITE_PROTECT <= err && ERROR_SHARING_BUFFER_EXCEEDED >= err) + return EACCES; + + if (ERROR_INVALID_STARTING_CODESEG <= err + && ERROR_INFLOOP_IN_RELOC_CHAIN >= err) + { + return ENOEXEC; + } + + switch (err) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + return ENOENT; + + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_DRIVE_LOCKED: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + return EACCES; + + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + case ERROR_DIRECT_ACCESS_HANDLE: + return EBADF; + + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_NOT_ENOUGH_QUOTA: + return ENOMEM; + + case ERROR_BAD_ENVIRONMENT: + return E2BIG; + + case ERROR_BAD_FORMAT: + return ENOEXEC; + + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + + case ERROR_FILE_EXISTS: + return EEXIST; + + case ERROR_NO_PROC_SLOTS: + case ERROR_MAX_THRDS_REACHED: + case ERROR_NESTING_NOT_ALLOWED: + return EAGAIN; + + case ERROR_BROKEN_PIPE: + return EPIPE; + + case ERROR_DISK_FULL: + return ENOSPC; + + case ERROR_WAIT_NO_CHILDREN: + case ERROR_CHILD_NOT_COMPLETE: + return ECHILD; + + case ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY; + + case ERROR_ALREADY_EXISTS: + return EEXIST; + } + + return EINVAL; +} + +#else // #if !defined (_WIN32) + # include <sys/types.h> # include <sys/wait.h> // for waitpid() # include <unistd.h> // for fork(), execv(), access() -#endif // _WIN{32,64} +# include <setjmp.h> // for setjmp(), longjmp() +# include <signal.h> // for signal() +# include <time.h> // for nanosleep() /**************************************************************************/ +// splits command line to the array of parameters +// note: modifies the cmd string +// returns the number of parameters in cmd +// if argv != 0 fills argv up to size elements +static size_t +_rw_split_cmd (char* cmd, char** argv, size_t size) +{ + RW_ASSERT (0 != cmd); + + size_t ret = 0; + + for (char* end = cmd + strlen (cmd); cmd != end; /*do nothing*/) { + // skip the leading spaces + while (isspace (*cmd)) + ++cmd; + + if (end == cmd) + break; + + if (argv && ret < size) + argv [ret] = cmd; + + ++ret; + + if ('\'' == *cmd || '\"' == *cmd) { + char* const cmd1 = cmd + 1; + // search the closing quote + if (char* pos = strchr (cmd1, *cmd)) { + // found, remove the quotes + // remove the opening quote + memmove (cmd, cmd1, pos - cmd1); + // remove the closing quote + cmd = pos - 1; + memmove (cmd, pos + 1, end - pos); + end -= 2; + } + else { + // not found + break; + } + } + + // search the space + while (*cmd && !isspace (*cmd)) + ++cmd; + + if (cmd != end) + // found, replace to '\0' + *cmd++ = '\0'; + } + + return ret; +} + +#endif // #if defined (_WIN32) + +/**************************************************************************/ + static int _rw_vsystem (const char *cmd, va_list va) { @@ -83,7 +233,7 @@ if (ret) { -#if !defined (_WIN32) && !defined (_WIN64) +#ifndef _WIN32 if (-1 == ret) { // system() failed, e.g., because fork() failed @@ -107,7 +257,7 @@ "the command \"%s\" exited with status %d", buf, status); } -#else // if defined (_WIN32) || defined (_WIN64) +#else // if defined (_WIN32) // FIXME: make this more descriptive rw_error (0, __FILE__, __LINE__, @@ -140,87 +290,57 @@ /**************************************************************************/ -// splits command line to the array of parameters -// note: modifies the cmd string -// returns the number of parameters in cmd -// if argv != 0 fills argv up to size elements -static size_t -_rw_split_cmd (char* cmd, char** argv, size_t size) +static rw_pid_t +_rw_vprocess_create (const char* cmd, va_list va) { RW_ASSERT (0 != cmd); - size_t ret = 0; + char buffer [256]; + char *buf = buffer; - for (char* end = cmd + strlen (cmd); cmd != end; /*do nothing*/) { - // skip the leading spaces - while (isspace (*cmd)) - ++cmd; + size_t bufsize = sizeof (buffer); - if (end == cmd) - break; + rw_vasnprintf (&buf, &bufsize, cmd, va); - if (argv && ret < size) - argv [ret] = cmd; + rw_pid_t ret = -1; - ++ret; +#ifdef _WIN32 - if ('\'' == *cmd || '\"' == *cmd) { - char* const cmd1 = cmd + 1; - // search the closing quote - if (char* pos = strchr (cmd1, *cmd)) { - // found, remove the quotes - // remove the opening quote - memmove (cmd, cmd1, pos - cmd1); - // remove the closing quote - cmd = pos - 1; - memmove (cmd, pos + 1, end - pos); - end -= 2; - } else { - // not found - break; - } - } + STARTUPINFO si = { sizeof (si) }; + PROCESS_INFORMATION pi; + if (CreateProcess (0, buf, 0, 0, FALSE, + CREATE_NEW_PROCESS_GROUP, 0, 0, &si, &pi)) { + CloseHandle (pi.hThread); + ret = rw_pid_t (pi.hProcess); + } + else { + const DWORD err = GetLastError (); - // search the space - while (*cmd && !isspace (*cmd)) - ++cmd; + rw_error (0, __FILE__, __LINE__, + "CreateProcess () failed: GetLastError() = %zu", + size_t (err)); - if (cmd != end) - // found, replace to '\0' - *cmd++ = '\0'; + if (ERROR_INVALID_NAME == err) + errno = ENOENT; + else + errno = _rw_map_errno (err); } - return ret; -} +#else // #if !defined (_WIN32) -/**************************************************************************/ - -static rw_pid_t -_rw_vprocess_create (const char* cmd, va_list va) -{ - RW_ASSERT (0 != cmd); - - char buffer [256]; - char *buf = buffer; - - size_t bufsize = sizeof (buffer); - - rw_vasnprintf (&buf, &bufsize, cmd, va); - const size_t MAX_PARAMS = 63; char* argv [MAX_PARAMS + 1] = { 0 }; - int ret; - size_t argc = _rw_split_cmd (buf, argv, MAX_PARAMS); if (0 < argc && MAX_PARAMS >= argc) ret = rw_process_create (argv [0], argv); else { - ret = -1; - errno = ENOMEM; + errno = E2BIG; } +#endif + if (buf != buffer) free (buf); @@ -235,7 +355,7 @@ va_list va; va_start (va, cmd); - const int ret = _rw_vprocess_create (cmd, va); + const rw_pid_t ret = _rw_vprocess_create (cmd, va); va_end (va); return ret; @@ -246,77 +366,194 @@ _TEST_EXPORT rw_pid_t rw_process_create (const char* path, char* const argv[]) { -#if defined (_WIN32) || defined (_WIN64) +#if defined (_WIN32) - const rw_pid_t child_pid = spawnvp (P_NOWAIT, path, argv); + return rw_process_create ("\"%s\" %{ As}", path, argv + 1); - if (-1 == child_pid) - rw_error (0, __FILE__, __LINE__, - "spawnp (P_NOWAIT, %#s, %{As}) failed: errno = %{#m} (%{m})", - path); +#else // #if !defined (_WIN32) -#else + if (0 == access (path, X_OK)) { - rw_pid_t child_pid = -1; + const rw_pid_t child_pid = fork (); - const int res = access (path, X_OK); - - if (0 == res) { - - child_pid = fork (); - if (0 == child_pid) { // the child process execvp (path, argv); // the execvp returns only if an error occurs - rw_fprintf (rw_stderr, "%s:%d execvp() failed: " - "errno = %{#m} (%{m})\n"); + rw_fprintf (rw_stderr, "%s:%d execvp (%#s, %{As}) failed: " + "errno = %{#m} (%{m})\n", path, argv); exit (1); } else if (-1 == child_pid) rw_error (0, __FILE__, __LINE__, - "fork() failed: errno = %{#m} (%{m})"); + "fork () failed: errno = %{#m} (%{m})"); + + return child_pid; } else rw_error (0, __FILE__, __LINE__, - "access(%#s, X_OK) failed: errno = %{#m} (%{m})", + "access (%#s, X_OK) failed: errno = %{#m} (%{m})", path); -#endif // #if defined (_WIN32) || defined (_WIN64) + return -1; - return child_pid; +#endif // #if defined (_WIN32) } /**************************************************************************/ +#if defined (_WIN32) + _TEST_EXPORT rw_pid_t -rw_waitpid (rw_pid_t pid, int* result) +rw_waitpid (rw_pid_t pid, int* result, int timeout/* = -1*/) { -#if defined (_WIN32) || defined (_WIN64) + /* Explicitly check for process_id being -1 or -2. In Windows NT, + * -1 is a handle on the current process, -2 is a handle on the + * current thread, and it is perfectly legal to to wait (forever) + * on either */ + if (-1 == pid || -2 == pid) { + errno = ECHILD; + return -1; + } - const rw_pid_t ret = cwait (result, pid, WAIT_CHILD); + const HANDLE handle = HANDLE (pid); - if (-1 == ret) + const DWORD milliseconds = + 0 > timeout ? INFINITE : DWORD (timeout * 1000); + + const DWORD res = WaitForSingleObject (handle, milliseconds); + + DWORD err = ERROR_SUCCESS; + + if (WAIT_OBJECT_0 == res) { + + DWORD dwExitCode; + if (GetExitCodeProcess (handle, &dwExitCode)) { + + CloseHandle (handle); + + if (dwExitCode) + rw_error (0, __FILE__, __LINE__, + "the process (pid=%li) exited with return code %d", + long (pid), int (dwExitCode)); + + if (result) + *result = int (dwExitCode); + + return pid; + } + + err = GetLastError (); rw_error (0, __FILE__, __LINE__, - "cwait(%#p, %li, WAIT_CHILD) failed: errno = %{#m} (%{m})", - result, pid); + "GetExitCodeProcess (%li, %#p) failed: GetLastError() = %zu", + long (pid), &dwExitCode, size_t (err)); + } + else if (WAIT_FAILED == res) { + err = GetLastError (); + rw_error (0, __FILE__, __LINE__, + "WaitForSingleObject (%li, %{?}INFINITE%{:}%zu%{;}) failed: " + "GetLastError() = %zu", + long (pid), INFINITE == milliseconds, + size_t (milliseconds), size_t (err)); + } + else { + // time-out elapsed + RW_ASSERT (WAIT_TIMEOUT == res); + return 0; + } -#else + if (ERROR_INVALID_HANDLE == err) + errno = ECHILD; + else + errno = _rw_map_errno (err); + return -1; +} + +#else // #if !defined (_WIN32) + +extern "C" { + +typedef void sig_handler_t (int); + +static void +sig_handler (int) +{ + // restore the signal + signal (SIGCHLD, sig_handler); +} + +} + +_TEST_EXPORT rw_pid_t +rw_waitpid (rw_pid_t pid, int* result, int timeout/* = -1*/) +{ int status = 0; - const rw_pid_t ret = waitpid (pid, &status, 0); + const int options = 0 > timeout ? 0 : WNOHANG; + rw_pid_t ret = waitpid (pid, &status, options); + if (-1 == ret) + rw_error (0, __FILE__, __LINE__, + "waitpid (%li, %#p, %{?}WNOHANG%{:}%i%{;}) failed: " + "errno = %{#m} (%{m})", + long (pid), &status, WNOHANG == options, options); + + if (0 < timeout && 0 == ret) { + // process still active, wait + sig_handler_t* old_handler = signal (SIGCHLD, sig_handler); + timespec rem = { timeout, 0 }; + + do { + timespec req = rem; + if (-1 == nanosleep (&req, &rem)) { + if (EINTR == errno) { + // possible that the child has exited + ret = waitpid (pid, &status, WNOHANG); + if (-1 == ret) { + rw_error (0, __FILE__, __LINE__, + "waitpid (%li, %#p, WNOHANG) failed: " + "errno = %{#m} (%{m})", + long (pid), &status); + } + else if (0 == ret) { + // child still active + continue; + } + else { + // child has exited + RW_ASSERT (pid == ret); + } + } + else { + rw_error (0, __FILE__, __LINE__, + "nanosleep (&{%i, 0}, %#p) failed: " + "errno = %{#m} (%{m})", + timeout, &rem); + + ret = -1; + } + } + else { + // timeout elapsed + RW_ASSERT (0 == ret); + } + } + while (false); + + signal (SIGCHLD, old_handler); + } + if (ret == pid) { - + if (WIFSIGNALED (status)) { // process exited with a signal const int signo = WTERMSIG (status); rw_error (0, __FILE__, __LINE__, - "the process (pid=%ld) exited with signal %d (%{K})", - long (ret), signo, signo); + "the process (pid=%li) exited with signal %d (%{K})", + long (pid), signo, signo); if (result) *result = signo; @@ -327,8 +564,8 @@ if (retcode) rw_error (0, __FILE__, __LINE__, - "the process (pid=%ld) exited with return code %d", - long (ret), retcode); + "the process (pid=%li) exited with return code %d", + long (pid), retcode); if (result) *result = retcode; @@ -337,12 +574,90 @@ *result = -1; } + return ret; +} + +#endif // #if defined (_WIN32) + + +_TEST_EXPORT int +rw_process_kill (rw_pid_t pid, int signo) +{ + // timeout for rw_wait_pid + const int timeout = 1000; + +#if defined (_WIN32) + + // send signal + if (!TerminateProcess (HANDLE (pid), DWORD (signo))) { + + const DWORD err = GetLastError (); + rw_error (0, __FILE__, __LINE__, + "TerminateProcess (%li, %i) failed: GetLastError() = %zu", + long (pid), signo, size_t (err)); + + if (ERROR_INVALID_HANDLE == err) + errno = ESRCH; + else if (ERROR_ACCESS_DENIED == err) + errno = EPERM; + else + errno = _rw_map_errno (err); + + return -1; + } + + // wait for process termination + int ret = rw_waitpid (pid, 0, timeout); + if (pid == ret) + return 0; + if (-1 == ret) rw_error (0, __FILE__, __LINE__, - "waitpid(%li, %#p, 0) failed: errno = %{#m} (%{m})", - pid, &status); + "rw_waitpid (%li, 0, %i) failed: errno = %{#m} (%{m})", + long (pid), timeout); -#endif // #if defined (_WIN32) || defined (_WIN64) + return 1; +#else // #if !defined (_WIN32) + + static const int signals_ [] = { + SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGKILL + }; + + const int* const signals = (-1 == signo) ? signals_ : &signo; + + const unsigned sigcount = + (-1 == signo) ? sizeof (signals_) / sizeof (*signals_) : 1; + + int ret = -1; + + for (unsigned i = 0; i < sigcount; ++i) { + + // send signal + ret = kill (pid, signals [i]); + + if (-1 == ret) { + rw_error (0, __FILE__, __LINE__, + "kill (%li, %{K}) failed: errno = %{#m} (%{m})", + long (pid), signals [i]); + + continue; + } + + // wait for process termination + ret = rw_waitpid (pid, 0, timeout); + if (pid == ret) + return 0; + + if (-1 == ret) + rw_error (0, __FILE__, __LINE__, + "rw_waitpid (%li, 0, %i) failed: errno = %{#m} (%{m})", + long (pid), timeout); + + ret = 1; + } + return ret; + +#endif // #if defined (_WIN32) }
