| /************************************************************************ |
| * |
| * process.cpp - definitions of testsuite process helpers |
| * |
| * $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. |
| * |
| **************************************************************************/ |
| |
| // expand _TEST_EXPORT macros |
| #define _RWSTD_TEST_SRC |
| |
| #include <ctype.h> // for isspace() |
| #include <errno.h> // for EACCES, errno |
| #include <signal.h> // for SIGXXX, kill() |
| #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() |
| |
| #include <sys/types.h> // for pid_t |
| |
| #include <rw_process.h> |
| #include <rw_driver.h> // for rw_note(), ... |
| #include <rw_printf.h> // for rw_fprintf() |
| |
| #ifdef __CYGWIN__ |
| // use the Windows API on Cygwin |
| # define _WIN32 |
| #endif |
| |
| #ifndef E2BIG |
| # define E2BIG 7 /* AIX, HP-UX, Linux, Solaris */ |
| #endif |
| |
| #ifndef EINTR |
| # define EINTR 4 |
| #endif |
| |
| #ifndef SIGCHLD |
| # if defined (_RWSTD_OS_AIX) || defined (_RWSTD_OS_OSF) |
| // AIX, Tru64 |
| # define SIGCHLD 20 |
| # elif defined (_RWSTD_OS_LINUX) |
| # define SIGCHLD 17 |
| # else |
| // (System V-based) HP-UX, IRIX, and Solaris |
| # define SIGCHLD 18 |
| # endif |
| #endif |
| |
| // all known Unices |
| #ifndef SIGALRM |
| # define SIGALRM 14 |
| #endif |
| |
| #ifndef SIGHUP |
| # define SIGHUP 1 |
| #endif |
| |
| #ifndef SIGQUIT |
| # define SIGQUIT 3 |
| #endif |
| |
| #ifndef SIGKILL |
| # define SIGKILL 9 |
| #endif |
| |
| |
| #ifdef _RWSTD_EDG_ECCP |
| |
| extern "C" { |
| |
| int kill (pid_t, int); |
| |
| } // extern "C" |
| |
| #endif // _RWSTD_EDG_ECCP |
| |
| /**************************************************************************/ |
| |
| _TEST_EXPORT int |
| rw_vasnprintf (char**, size_t*, const char*, va_list); |
| |
| /**************************************************************************/ |
| |
| #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_INVALID_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(), sleep() |
| # include <setjmp.h> // for setjmp(), longjmp() |
| # include <signal.h> // for signal() |
| # include <time.h> // for time() |
| |
| /**************************************************************************/ |
| |
| // 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 |
| char* const pos = strchr (cmd1, *cmd); |
| if (pos) { |
| // 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) |
| { |
| RW_ASSERT (0 != cmd); |
| |
| char *buf = 0; |
| |
| rw_vasnprintf (&buf, 0, cmd, va); |
| |
| rw_note (0, "file:" __FILE__, __LINE__, "executing \"%s\"", buf); |
| |
| // avoid using const in order to prevent gcc warning on Linux |
| // issued for WIFSIGNALED() et al: cast from `const int*' to |
| // `int*' discards qualifiers from pointer target type: |
| // see http://sourceware.org/bugzilla/show_bug.cgi?id=1392 |
| /* const */ int ret = system (buf); |
| |
| if (ret) { |
| |
| #ifndef _WIN32 |
| |
| if (-1 == ret) { |
| // system() failed, e.g., because fork() failed |
| rw_error (0, __FILE__, __LINE__, |
| "system (\"%s\") failed: errno = %{#m} (%{m})", |
| buf); |
| } |
| else if (WIFSIGNALED (ret)) { |
| // command exited with a signal |
| const int signo = WTERMSIG (ret); |
| |
| rw_error (0, __FILE__, __LINE__, |
| "the command \"%s\" exited with signal %d (%{K})", |
| buf, signo, signo); |
| } |
| else { |
| // command exited with a non-zero status |
| const int status = WEXITSTATUS (ret); |
| |
| rw_error (0, __FILE__, __LINE__, |
| "the command \"%s\" exited with status %d", |
| buf, status); |
| } |
| #else // if defined (_WIN32) |
| |
| // FIXME: make this more descriptive |
| rw_error (0, __FILE__, __LINE__, |
| "the command \"%s\" failed with code %d", |
| buf, ret); |
| |
| #endif // _WIN32 |
| |
| } |
| |
| free (buf); |
| |
| return ret; |
| } |
| |
| /**************************************************************************/ |
| |
| _TEST_EXPORT int |
| rw_system (const char *cmd, ...) |
| { |
| va_list va; |
| va_start (va, cmd); |
| |
| const int ret = _rw_vsystem (cmd, va); |
| |
| va_end (va); |
| return ret; |
| } |
| |
| /**************************************************************************/ |
| |
| static rw_pid_t |
| _rw_vprocess_create (const char* cmd, va_list va) |
| { |
| RW_ASSERT (0 != cmd); |
| |
| char *buf = 0; |
| |
| rw_vasnprintf (&buf, 0, cmd, va); |
| |
| rw_pid_t ret = -1; |
| |
| #ifdef _WIN32 |
| |
| 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 (); |
| |
| rw_error (0, __FILE__, __LINE__, |
| "CreateProcess () failed: GetLastError() = %zu", |
| size_t (err)); |
| |
| errno = _rw_map_errno (err); |
| } |
| |
| #else // #if !defined (_WIN32) |
| |
| const size_t MAX_PARAMS = 63; |
| char* argv [MAX_PARAMS + 1] = { 0 }; |
| |
| size_t argc = _rw_split_cmd (buf, argv, MAX_PARAMS); |
| |
| if (0 < argc && MAX_PARAMS >= argc) |
| ret = rw_process_create (argv [0], argv); |
| else |
| errno = E2BIG; |
| |
| #endif // _WIN32 |
| |
| free (buf); |
| |
| return ret; |
| } |
| |
| /**************************************************************************/ |
| |
| _TEST_EXPORT rw_pid_t |
| rw_process_create (const char* cmd, ...) |
| { |
| va_list va; |
| va_start (va, cmd); |
| |
| const rw_pid_t ret = _rw_vprocess_create (cmd, va); |
| |
| va_end (va); |
| return ret; |
| } |
| |
| /**************************************************************************/ |
| |
| _TEST_EXPORT rw_pid_t |
| rw_process_create (const char* path, char* const argv[]) |
| { |
| #if defined (_WIN32) |
| |
| return rw_process_create ("\"%s\" %{ As}", path, argv + 1); |
| |
| #else // #if !defined (_WIN32) |
| |
| if (0 == access (path, X_OK)) { |
| |
| const rw_pid_t 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 (%#s, %{As}) failed: " |
| "errno = %{#m} (%{m})\n", |
| __FILE__, __LINE__, path, argv); |
| |
| exit (1); |
| } |
| else if (-1 == child_pid) |
| rw_error (0, __FILE__, __LINE__, |
| "fork () failed: errno = %{#m} (%{m})"); |
| |
| return child_pid; |
| } |
| else |
| rw_error (0, __FILE__, __LINE__, |
| "access (%#s, X_OK) failed: errno = %{#m} (%{m})", |
| path); |
| |
| return -1; |
| |
| #endif // #if defined (_WIN32) |
| } |
| |
| /**************************************************************************/ |
| |
| #if defined (_WIN32) |
| |
| _TEST_EXPORT rw_pid_t |
| rw_waitpid (rw_pid_t pid, int* result, int timeout/* = -1*/) |
| { |
| /* 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 HANDLE handle = HANDLE (pid); |
| |
| 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=%{P}) exited with return code %d", |
| pid, int (dwExitCode)); |
| |
| if (result) |
| *result = int (dwExitCode); |
| |
| return pid; |
| } |
| |
| err = GetLastError (); |
| rw_error (0, __FILE__, __LINE__, |
| "GetExitCodeProcess (%{P}, %#p) failed: GetLastError() = %zu", |
| pid, &dwExitCode, size_t (err)); |
| } |
| else if (WAIT_FAILED == res) { |
| err = GetLastError (); |
| rw_error (0, __FILE__, __LINE__, |
| "WaitForSingleObject (%{P}, %{?}INFINITE%{:}%zu%{;}) failed: " |
| "GetLastError() = %zu", |
| pid, INFINITE == milliseconds, |
| size_t (milliseconds), size_t (err)); |
| } |
| else { |
| // time-out elapsed |
| RW_ASSERT (WAIT_TIMEOUT == res); |
| return 0; |
| } |
| |
| if (ERROR_INVALID_HANDLE == err) |
| errno = ECHILD; |
| else |
| errno = _rw_map_errno (err); |
| |
| return -1; |
| } |
| |
| #else // #if !defined (_WIN32) |
| |
| extern "C" { |
| |
| static int alarm_timeout; |
| |
| static void handle_alarm_signal (int) { |
| alarm_timeout = 1; |
| } |
| |
| typedef void (*signal_handler_t) (int); |
| |
| } |
| |
| _TEST_EXPORT rw_pid_t |
| rw_waitpid (rw_pid_t pid, int* presult, int timeout/* = -1*/) |
| { |
| #ifdef _RWSTD_EDG_ECCP |
| # define _RWSTD_NO_SIGACTION |
| #endif |
| |
| #ifndef _RWSTD_NO_SIGACTION |
| struct sigaction prev_alarm_action; |
| #else |
| signal_handler_t prev_alarm_handler = 0; |
| #endif |
| int prev_alarm_timeout = 0; |
| |
| alarm_timeout = 0; |
| if (0 < timeout) { |
| |
| #ifndef _RWSTD_NO_SIGACTION |
| struct sigaction alarm_action; |
| memset (&alarm_action, 0, sizeof alarm_action); |
| |
| const signal_handler_t handler_fun = handle_alarm_signal; |
| memcpy (&alarm_action.sa_handler, &handler_fun, |
| sizeof alarm_action.sa_handler); |
| |
| sigaction (SIGALRM, &alarm_action, &prev_alarm_action); |
| #else |
| prev_alarm_handler = signal (SIGALRM, handle_alarm_signal); |
| #endif |
| prev_alarm_timeout = alarm (timeout); |
| |
| } |
| |
| int result = 0; |
| if (!presult) |
| presult = &result; |
| |
| const time_t start = time(0); |
| |
| int status = 0; |
| rw_pid_t ret = 0; |
| do { |
| |
| ret = waitpid (pid_t (pid), &status, 0); |
| |
| if (-1 == ret) { |
| |
| if (EINTR == errno && alarm_timeout) { |
| |
| // we are expected to return 0 on timeout |
| ret = 0; |
| } |
| else if (EINTR == errno) { |
| |
| rw_warn (0, __FILE__, __LINE__, |
| "waitpid (%{P}, %#p, 0) interrupted: " |
| "errno = %{#m} (%{m})", |
| pid, &status); |
| |
| continue; // try again |
| } |
| else { |
| |
| rw_error (0, __FILE__, __LINE__, |
| "waitpid (%{P}, %#p, 0) failed: " |
| "errno = %{#m} (%{m})", |
| pid, &status); |
| } |
| |
| } |
| else if (ret == pid) { |
| |
| if (WIFSIGNALED (status)) { |
| // process exited with a signal |
| const int signo = WTERMSIG (status); |
| |
| rw_error (0, __FILE__, __LINE__, |
| "the process (pid=%{P}) exited with signal %d (%{K})", |
| pid, signo, signo); |
| |
| *presult = signo; |
| } |
| else if (WIFEXITED (status)) { |
| // process exited with a status |
| const int retcode = WEXITSTATUS (status); |
| |
| if (retcode) |
| rw_error (0, __FILE__, __LINE__, |
| "the process (pid=%{P}) exited with return code %d", |
| pid, retcode); |
| |
| *presult = retcode; |
| } |
| else { |
| *presult = -1; |
| } |
| } |
| else { |
| *presult = -1; |
| } |
| |
| } while(false); |
| |
| if (0 < timeout) { |
| |
| if (prev_alarm_timeout) { |
| const int delta = int (time(0) - start); |
| |
| if (delta < prev_alarm_timeout) |
| prev_alarm_timeout -= delta; |
| else |
| prev_alarm_timeout = 1; |
| } |
| |
| alarm (prev_alarm_timeout); |
| |
| #ifndef _RWSTD_NO_SIGACTION |
| sigaction (SIGALRM, &prev_alarm_action, 0); |
| #else |
| signal (SIGALRM, prev_alarm_handler); |
| #endif |
| |
| } |
| |
| 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 = 1; |
| |
| #if defined (_WIN32) |
| |
| // send signal |
| if (!TerminateProcess (HANDLE (pid), DWORD (signo))) { |
| |
| const DWORD err = GetLastError (); |
| rw_error (0, __FILE__, __LINE__, |
| "TerminateProcess (%{P}, %i) failed: GetLastError() = %zu", |
| 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 |
| rw_pid_t res = rw_waitpid (pid, 0, timeout); |
| if (pid == res) |
| return 0; |
| |
| if (-1 == res) |
| rw_error (0, __FILE__, __LINE__, |
| "rw_waitpid (%{P}, 0, %i) failed: errno = %{#m} (%{m})", |
| pid, timeout); |
| |
| 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) ? unsigned (sizeof (signals_) / sizeof (*signals_)) : 1; |
| |
| int ret = -1; |
| |
| for (unsigned i = 0; i < sigcount; ++i) { |
| |
| // send signal |
| ret = kill (pid_t (pid), signals [i]); |
| |
| if (-1 == ret) { |
| rw_error (0, __FILE__, __LINE__, |
| "kill (%{P}, %{K}) failed: errno = %{#m} (%{m})", |
| pid, signals [i]); |
| |
| continue; |
| } |
| |
| // wait for process termination |
| rw_pid_t res = rw_waitpid (pid, 0, timeout); |
| if (pid == res) |
| return 0; |
| |
| if (-1 == res) |
| rw_error (0, __FILE__, __LINE__, |
| "rw_waitpid (%{P}, 0, %i) failed: errno = %{#m} (%{m})", |
| pid, timeout); |
| |
| ret = 1; |
| } |
| |
| return ret; |
| |
| #endif // #if defined (_WIN32) |
| } |