summaryrefslogtreecommitdiffstats
path: root/mysql-test/lib/My/SafeProcess
diff options
context:
space:
mode:
Diffstat (limited to 'mysql-test/lib/My/SafeProcess')
-rw-r--r--mysql-test/lib/My/SafeProcess/Base.pm227
-rw-r--r--mysql-test/lib/My/SafeProcess/CMakeLists.txt50
-rw-r--r--mysql-test/lib/My/SafeProcess/safe_kill_win.cc146
-rw-r--r--mysql-test/lib/My/SafeProcess/safe_process.cc364
-rw-r--r--mysql-test/lib/My/SafeProcess/safe_process_win.cc389
-rw-r--r--mysql-test/lib/My/SafeProcess/wsrep_check_version.c51
6 files changed, 1227 insertions, 0 deletions
diff --git a/mysql-test/lib/My/SafeProcess/Base.pm b/mysql-test/lib/My/SafeProcess/Base.pm
new file mode 100644
index 00000000..1cd01cb0
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/Base.pm
@@ -0,0 +1,227 @@
+# -*- cperl -*-
+# Copyright (c) 2007 MySQL AB, 2008, 2009 Sun Microsystems, Inc.
+# Use is subject to license terms.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
+
+# This is a library file used by the Perl version of mysql-test-run,
+# and is part of the translation of the Bourne shell script with the
+# same name.
+
+use strict;
+
+package My::SafeProcess::Base;
+
+#
+# Utility functions for Process management
+#
+
+use Carp;
+use IO::Pipe;
+
+use base qw(Exporter);
+our @EXPORT= qw(create_process);
+
+
+
+#
+# safe_fork
+# Retry a couple of times if fork returns EAGAIN
+#
+sub _safe_fork {
+ my $retries= 100;
+ my $pid;
+
+ FORK:
+ {
+ $pid= fork;
+ if ( not defined($pid)) {
+
+ croak("fork failed after: $!") if (!$retries--);
+
+ warn("fork failed sleep 1 second and redo: $!");
+ sleep(1);
+ redo FORK;
+ }
+ }
+
+ return $pid;
+};
+
+
+#
+# Decode exit status
+#
+sub exit_status {
+ my $self= shift;
+ my $raw= $self->{EXIT_STATUS};
+
+ croak("Can't call exit_status before process has died")
+ unless defined $raw;
+
+ if ($raw & 127)
+ {
+ # Killed by signal
+ my $signal_num= $raw & 127;
+ my $dumped_core= $raw & 128;
+ return 1; # Return error code
+ }
+ else
+ {
+ # Normal process exit
+ return $raw >> 8;
+ };
+}
+
+# threads.pm may not exist everywhere, so use only on Windows.
+
+use if $^O eq "MSWin32", "threads";
+use if $^O eq "MSWin32", "threads::shared";
+
+my $win32_spawn_lock :shared;
+
+
+#
+# Create a new process
+# Return pid of the new process
+#
+sub create_process {
+ my %opts=
+ (
+ @_
+ );
+
+ my $path = delete($opts{'path'}) or die "path required";
+ my $args = delete($opts{'args'}) or die "args required";
+ my $input = delete($opts{'input'});
+ my $output = delete($opts{'output'});
+ my $error = delete($opts{'error'});
+
+ my $open_mode= $opts{append} ? ">>" : ">";
+
+ if ($^O eq "MSWin32"){
+
+ lock($win32_spawn_lock);
+
+ #printf STDERR "stdin %d, stdout %d, stderr %d\n",
+ # fileno STDIN, fileno STDOUT, fileno STDERR;
+
+ # input output redirect
+ my ($oldin, $oldout, $olderr);
+ open $oldin, '<&', \*STDIN or die "Failed to save old stdin: $!";
+ open $oldout, '>&', \*STDOUT or die "Failed to save old stdout: $!";
+ open $olderr, '>&', \*STDERR or die "Failed to save old stderr: $!";
+
+ if ( $input ) {
+ if ( ! open(STDIN, "<", $input) ) {
+ croak("can't redirect STDIN to '$input': $!");
+ }
+ }
+
+ if ( $output ) {
+ if ( ! open(STDOUT, $open_mode, $output) ) {
+ croak("can't redirect STDOUT to '$output': $!");
+ }
+ }
+
+ if ( $error ) {
+ if ( $output eq $error ) {
+ if ( ! open(STDERR, ">&STDOUT") ) {
+ croak("can't dup STDOUT: $!");
+ }
+ }
+ elsif ( ! open(STDERR, $open_mode, $error) ) {
+ croak("can't redirect STDERR to '$error': $!");
+ }
+ }
+
+
+ # Magic use of 'system(1, @args)' to spawn a process
+ # and get a proper Win32 pid
+ unshift (@$args, $path);
+ my $pid= system(1, @$args);
+ if ( $pid == 0 ){
+ print $olderr "create_process failed: $^E\n";
+ die "create_process failed: $^E";
+ }
+
+ # Retore IO redirects
+ open STDERR, '>&', $olderr
+ or croak("unable to reestablish STDERR");
+ open STDOUT, '>&', $oldout
+ or croak("unable to reestablish STDOUT");
+ open STDIN, '<&', $oldin
+ or croak("unable to reestablish STDIN");
+ #printf STDERR "stdin %d, stdout %d, stderr %d\n",
+ # fileno STDIN, fileno STDOUT, fileno STDERR;
+ return $pid;
+
+ }
+
+ local $SIG{PIPE}= sub { print STDERR "Got signal $@\n"; };
+ my $pipe= IO::Pipe->new();
+ my $pid= _safe_fork();
+ if ($pid){
+ # Parent
+ $pipe->reader();
+ my $line= <$pipe>; # Wait for child to say it's ready
+ return $pid;
+ }
+
+ $SIG{INT}= 'DEFAULT';
+ $SIG{HUP}= 'DEFAULT';
+
+ # Make this process it's own process group to be able to kill
+ # it and any childs(that hasn't changed group themself)
+ setpgrp(0,0) if $opts{setpgrp};
+
+ if ( $output ) {
+ close STDOUT;
+ open(STDOUT, $open_mode, $output)
+ or croak "can't redirect STDOUT to '$output': $!";
+ }
+
+ if ( $error ) {
+ if ( defined $output and $output eq $error ) {
+ if ( ! open(STDERR, ">&STDOUT") ) {
+ croak("can't dup STDOUT: $!");
+ }
+ }
+ else {
+ close STDERR;
+ open(STDERR, $open_mode, $error)
+ or croak "can't redirect STDERR to '$error': $!";
+ }
+ }
+
+ if ( $input ) {
+ if ( ! open(STDIN, "<", $input) ) {
+ croak("can't redirect STDIN to '$input': $!");
+ }
+ }
+
+ # Tell parent to continue
+ $pipe->writer();
+ print $pipe "ready\n";
+
+ if ( !exec($path, @$args) ){
+ croak("Failed to exec '$path': $!");
+ }
+
+ croak("Should never come here");
+
+}
+
+1;
+
diff --git a/mysql-test/lib/My/SafeProcess/CMakeLists.txt b/mysql-test/lib/My/SafeProcess/CMakeLists.txt
new file mode 100644
index 00000000..0004a449
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/CMakeLists.txt
@@ -0,0 +1,50 @@
+# Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
+
+
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include)
+
+IF (WIN32)
+ ADD_EXECUTABLE(my_safe_process safe_process_win.cc)
+ ADD_EXECUTABLE(my_safe_kill safe_kill_win.cc)
+ TARGET_INCLUDE_DIRECTORIES(my_safe_kill PRIVATE ${CMAKE_SOURCE_DIR}/include)
+ TARGET_LINK_LIBRARIES(my_safe_kill mysys psapi)
+ELSE()
+ ADD_EXECUTABLE(my_safe_process safe_process.cc)
+ENDIF()
+
+IF(WITH_WSREP)
+ ADD_EXECUTABLE(wsrep_check_version wsrep_check_version.c)
+ TARGET_LINK_LIBRARIES(wsrep_check_version ${CMAKE_DL_LIBS})
+ENDIF()
+
+IF(NOT INSTALL_MYSQLTESTDIR)
+ RETURN()
+ENDIF()
+
+SET(INSTALL_ARGS
+ DESTINATION "${INSTALL_MYSQLTESTDIR}/lib/My/SafeProcess"
+ COMPONENT Test
+)
+
+INSTALL(TARGETS my_safe_process ${INSTALL_ARGS})
+IF(WITH_WSREP)
+ INSTALL(TARGETS wsrep_check_version ${INSTALL_ARGS})
+ENDIF()
+IF (WIN32)
+ INSTALL(TARGETS my_safe_kill ${INSTALL_ARGS})
+ENDIF()
+
+INSTALL(FILES Base.pm ${INSTALL_ARGS})
diff --git a/mysql-test/lib/My/SafeProcess/safe_kill_win.cc b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc
new file mode 100644
index 00000000..375ed80b
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_kill_win.cc
@@ -0,0 +1,146 @@
+/* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+
+/*
+ Utility program used to signal a safe_process it's time to shutdown
+
+ Usage:
+ safe_kill <pid>
+*/
+
+#include <windows.h>
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <psapi.h>
+#include <my_minidump.h>
+
+#include <tlhelp32.h>
+#include <vector>
+
+
+static std::vector<DWORD> find_children(DWORD pid)
+{
+ HANDLE h= NULL;
+ PROCESSENTRY32 pe={ 0 };
+ std::vector<DWORD> children;
+
+ pe.dwSize = sizeof(PROCESSENTRY32);
+ h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if(h == INVALID_HANDLE_VALUE)
+ return children;
+
+ for (BOOL ret = Process32First(h, &pe); ret; ret = Process32Next(h, &pe))
+ {
+ if (pe.th32ParentProcessID == pid)
+ children.push_back(pe.th32ProcessID);
+ }
+ CloseHandle(h);
+ return children;
+}
+
+
+static int create_dump(DWORD pid, int recursion_depth= 5)
+{
+ if (recursion_depth < 0)
+ return 0;
+
+ my_create_minidump(pid, TRUE);
+ std::vector<DWORD> children= find_children(pid);
+ for(size_t i=0; i < children.size(); i++)
+ create_dump(children[i], recursion_depth -1);
+ return 0;
+}
+
+
+int main(int argc, const char** argv )
+{
+ DWORD pid= -1;
+ HANDLE shutdown_event;
+ char safe_process_name[32]= {0};
+ int retry_open_event= 2;
+ /* Ignore any signals */
+ signal(SIGINT, SIG_IGN);
+ signal(SIGBREAK, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+
+ if ((argc != 2 && argc != 3) || (argc == 3 && strcmp(argv[2],"dump"))) {
+ fprintf(stderr, "safe_kill <pid> [dump]\n");
+ exit(2);
+ }
+ pid= atoi(argv[1]);
+
+ if (argc == 3)
+ {
+ return create_dump(pid);
+ }
+ _snprintf(safe_process_name, sizeof(safe_process_name),
+ "safe_process[%d]", pid);
+
+ /* Open the event to signal */
+ while ((shutdown_event=
+ OpenEvent(EVENT_MODIFY_STATE, FALSE, safe_process_name)) == NULL)
+ {
+ /*
+ Check if the process is alive, otherwise there is really
+ no sense to retry the open of the event
+ */
+ HANDLE process;
+ DWORD exit_code;
+ process= OpenProcess(SYNCHRONIZE| PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (!process)
+ {
+ /* Already died */
+ exit(1);
+ }
+
+ if (!GetExitCodeProcess(process,&exit_code))
+ {
+ fprintf(stderr, "GetExitCodeProcess failed, pid= %lu, err= %lu\n",
+ pid, GetLastError());
+ exit(1);
+ }
+
+ if (exit_code != STILL_ACTIVE)
+ {
+ /* Already died */
+ CloseHandle(process);
+ exit(2);
+ }
+
+ CloseHandle(process);
+
+ if (retry_open_event--)
+ Sleep(100);
+ else
+ {
+ fprintf(stderr, "Failed to open shutdown_event '%s', error: %lu\n",
+ safe_process_name, GetLastError());
+ exit(3);
+ }
+ }
+
+ if(SetEvent(shutdown_event) == 0)
+ {
+ fprintf(stderr, "Failed to signal shutdown_event '%s', error: %lu\n",
+ safe_process_name, GetLastError());
+ CloseHandle(shutdown_event);
+ exit(4);
+ }
+ CloseHandle(shutdown_event);
+ exit(0);
+}
+
diff --git a/mysql-test/lib/My/SafeProcess/safe_process.cc b/mysql-test/lib/My/SafeProcess/safe_process.cc
new file mode 100644
index 00000000..dcf9491d
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_process.cc
@@ -0,0 +1,364 @@
+/* Copyright (c) 2008, 2012, Oracle and/or its affiliates
+ Copyright (c) 2019, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+
+/*
+ Utility program that encapsulates process creation, monitoring
+ and bulletproof process cleanup
+
+ Usage:
+ safe_process [options to safe_process] -- progname arg1 ... argn
+
+ To safeguard mysqld you would invoke safe_process with a few options
+ for safe_process itself followed by a double dash to indicate start
+ of the command line for the program you really want to start
+
+ $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
+
+ This would redirect output to output.log and then start mysqld,
+ once it has done that it will continue to monitor the child as well
+ as the parent.
+
+ The safe_process then checks the follwing things:
+ 1. Child exits, propagate the childs return code to the parent
+ by exiting with the same return code as the child.
+
+ 2. Parent dies, immediately kill the child and exit, thus the
+ parent does not need to properly cleanup any child, it is handled
+ automatically.
+
+ 3. Signal's recieced by the process will trigger same action as 2)
+
+*/
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+int verbose= 0;
+int terminated= 0;
+pid_t child_pid= -1;
+char safe_process_name[32]= {0};
+
+
+static void message(const char* fmt, ...)
+{
+ if (!verbose)
+ return;
+ va_list args;
+ fprintf(stderr, "%s: ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ fflush(stderr);
+}
+
+
+static void die(const char* fmt, ...)
+{
+ va_list args;
+ fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ if (int last_err= errno)
+ fprintf(stderr, "error: %d, %s\n", last_err, strerror(last_err));
+ exit(6);
+}
+
+
+#ifdef __APPLE__
+#include <sys/sysctl.h>
+
+
+/*
+ Eventually we may want to adopt kern.corefile parsing code from
+ https://opensource.apple.com/source/xnu/xnu-3247.1.106/bsd/kern/kern_proc.c
+*/
+
+void handle_core(pid_t pid)
+{
+ char corefile[256];
+ int coredump;
+ size_t corefile_size= sizeof(corefile);
+ size_t coredump_size= sizeof(coredump);
+
+ if (sysctlbyname("kern.coredump", &coredump, &coredump_size, 0, 0) ||
+ sysctlbyname("kern.corefile", corefile, &corefile_size, 0, 0))
+ {
+ message("sysctlbyname failed: %d (%s)", errno, strerror(errno));
+ return;
+ }
+
+ if (!coredump)
+ {
+ message("core dumps disabled, to enable run sudo sysctl kern.coredump=1");
+ return;
+ }
+
+ if (!strncmp(corefile, "/cores/core.%P", corefile_size))
+ {
+ char from[256];
+ char *to= from + 7;
+
+ snprintf(from, sizeof(from), "/cores/core.%u", pid);
+ if (!access(from, R_OK))
+ {
+ if (symlink(from, to))
+ message("symlink failed: %d (%s)", errno, strerror(errno));
+ }
+ }
+}
+#else
+void handle_core(pid_t pid __attribute__((unused))) {}
+#endif
+
+
+static int kill_child(bool was_killed)
+{
+ int status= 0;
+ pid_t ret_pid= 0;
+
+ message("Killing child: %d", child_pid);
+ // Terminate whole process group
+ if (! was_killed)
+ {
+ kill(-child_pid, SIGTERM);
+ sleep(10); // will be interrupted by SIGCHLD
+ if (!(ret_pid= waitpid(child_pid, &status, WNOHANG)))
+ kill(-child_pid, SIGKILL);
+ }
+
+ if (!ret_pid)
+ ret_pid= waitpid(child_pid, &status, 0);
+ if (ret_pid == child_pid)
+ {
+ int exit_code= 1;
+ if (WIFEXITED(status))
+ {
+ // Process has exited, collect return status
+ exit_code= WEXITSTATUS(status);
+ message("Child exit: %d", exit_code);
+ // Exit with exit status of the child
+ return exit_code;
+ }
+
+ if (WIFSIGNALED(status))
+ {
+ message("Child killed by signal: %d", WTERMSIG(status));
+ handle_core(child_pid);
+ }
+
+ return exit_code;
+ }
+ return 5;
+}
+
+
+extern "C" void handle_abort(int sig)
+{
+ message("Got signal %d, child_pid: %d, sending ABRT", sig, child_pid);
+
+ if (child_pid > 0) {
+ kill(-child_pid, SIGABRT); // Don't wait for it to terminate
+ }
+}
+
+
+extern "C" void handle_signal(int sig)
+{
+ message("Got signal %d, child_pid: %d", sig, child_pid);
+ terminated= 1;
+
+ if (child_pid > 0)
+ _exit(kill_child(sig == SIGCHLD));
+
+ // Ignore further signals
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ // Continune execution, allow the child to be started and
+ // finally terminated by monitor loop
+}
+
+
+void setlimit(int what, uint soft, uint hard)
+{
+ struct rlimit lim = { soft, hard };
+ if (setrlimit (what, &lim) < 0)
+ message("setrlimit failed, errno=%d", errno);
+}
+
+
+int main(int argc, char* const argv[] )
+{
+ char* const* child_argv= 0;
+ pid_t own_pid= getpid();
+ pid_t parent_pid= getppid();
+ bool nocore = false;
+ struct sigaction sa,sa_abort;
+
+ sa.sa_handler= handle_signal;
+ sa.sa_flags= SA_NOCLDSTOP;
+ sigemptyset(&sa.sa_mask);
+
+ sa_abort.sa_handler= handle_abort;
+ sa_abort.sa_flags= 0;
+ sigemptyset(&sa_abort.sa_mask);
+ /* Install signal handlers */
+ sigaction(SIGTERM, &sa,NULL);
+ sigaction(SIGINT, &sa,NULL);
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGCHLD, &sa,NULL);
+ sigaction(SIGABRT, &sa_abort,NULL);
+
+ sprintf(safe_process_name, "safe_process[%ld]", (long) own_pid);
+
+ message("Started");
+
+ /* Parse arguments */
+ for (int i= 1; i < argc; i++) {
+ const char* arg= argv[i];
+ if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
+ /* Got the "--" delimiter */
+ if (i >= argc)
+ die("No real args -> nothing to do");
+ child_argv= &argv[i+1];
+ break;
+ } else {
+ if ( strcmp(arg, "--verbose") == 0 )
+ verbose++;
+ else if ( strncmp(arg, "--parent-pid", 12) == 0 )
+ {
+ /* Override parent_pid with a value provided by user */
+ const char* start;
+ if ((start= strstr(arg, "=")) == NULL)
+ die("Could not find start of option value in '%s'", arg);
+ start++; /* Step past = */
+ if ((parent_pid= atoi(start)) == 0)
+ die("Invalid value '%s' passed to --parent-id", start);
+ }
+ else if ( strcmp(arg, "--nocore") == 0 )
+ {
+ nocore = true; // Don't allow the process to dump core
+ }
+ else if ( strncmp (arg, "--env ", 6) == 0 )
+ {
+ putenv(strdup(arg+6));
+ }
+ else
+ die("Unknown option: %s", arg);
+ }
+ }
+ if (!child_argv || *child_argv == 0)
+ die("nothing to do");
+
+ message("parent_pid: %d", parent_pid);
+ if (parent_pid == own_pid)
+ die("parent_pid is equal to own pid!");
+
+ char buf;
+ int pfd[2];
+ if (pipe(pfd) == -1)
+ die("Failed to create pipe");
+
+ /* Create the child process */
+ while((child_pid= fork()) == -1)
+ {
+ message("fork failed");
+ sleep(1);
+ }
+
+ /*
+ Child: Make this process it's own process group to be able to kill
+ it and any its children that hasn't changed a group themselves)
+
+ Parent: Detach from the parent's process group, so that killing a parent
+ group wouldn't kill us (if we're killed, there's no one to kill our child
+ processes that run in their own process group). There's a loop below
+ that monitors the parent, it's enough.
+ */
+ setpgid(0, 0);
+
+
+ if (child_pid == 0)
+ {
+ close(pfd[0]); // Close unused read end
+
+ // Use default signal handlers in child
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+
+ if (nocore)
+ setlimit(RLIMIT_CORE, 0, 0);
+
+ /*
+ mysqld defaults depend on that. make test results stable and independent
+ from the environment
+ */
+ setlimit(RLIMIT_NOFILE, 1024, 1024);
+
+ // Signal that child is ready
+ buf= 37;
+ if ((write(pfd[1], &buf, 1)) < 1)
+ die("Failed to signal that child is ready");
+ // Close write end
+ close(pfd[1]);
+
+ execvp(child_argv[0], child_argv);
+ die("Failed to exec child");
+ }
+
+ close(pfd[1]); // Close unused write end
+
+ // Wait for child to signal it's ready
+ if ((read(pfd[0], &buf, 1)) < 1)
+ die("Failed to read signal from child");
+
+ if (buf != 37)
+ die("Didn't get 37 from pipe");
+ close(pfd[0]); // Close read end
+
+ /* Monitor loop */
+ message("Started child %d, terminated: %d", child_pid, terminated);
+
+ while (!terminated)
+ {
+ // Check if parent is still alive
+ if (kill(parent_pid, 0) != 0)
+ {
+ message("Parent is not alive anymore");
+ break;
+ }
+ /* Wait for parent or child to die */
+ sleep(1);
+ }
+ return kill_child(0);
+}
+
diff --git a/mysql-test/lib/My/SafeProcess/safe_process_win.cc b/mysql-test/lib/My/SafeProcess/safe_process_win.cc
new file mode 100644
index 00000000..8a5bb60a
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/safe_process_win.cc
@@ -0,0 +1,389 @@
+/* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+
+/*
+ Utility program that encapsulates process creation, monitoring
+ and bulletproof process cleanup
+
+ Usage:
+ safe_process [options to safe_process] -- progname arg1 ... argn
+
+ To safeguard mysqld you would invoke safe_process with a few options
+ for safe_process itself followed by a double dash to indicate start
+ of the command line for the program you really want to start
+
+ $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
+
+ This would redirect output to output.log and then start mysqld,
+ once it has done that it will continue to monitor the child as well
+ as the parent.
+
+ The safe_process then checks the follwing things:
+ 1. Child exits, propagate the childs return code to the parent
+ by exiting with the same return code as the child.
+
+ 2. Parent dies, immediately kill the child and exit, thus the
+ parent does not need to properly cleanup any child, it is handled
+ automatically.
+
+ 3. Signal's recieced by the process will trigger same action as 2)
+
+ 4. The named event "safe_process[pid]" can be signaled and will
+ trigger same action as 2)
+
+ WARNING! Be careful when using ProcessExplorer, since it will open
+ a handle to each process(and maybe also the Job), the process
+ spawned by safe_process will not be closed off when safe_process
+ is killed.
+*/
+
+#include <windows.h>
+#include <stdio.h>
+#include <tlhelp32.h>
+#include <signal.h>
+#include <stdlib.h>
+
+static int verbose= 0;
+static char safe_process_name[32]= {0};
+
+static void message(const char* fmt, ...)
+{
+ if (!verbose)
+ return;
+ va_list args;
+ fprintf(stderr, "%s: ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ fflush(stderr);
+}
+
+
+static void die(const char* fmt, ...)
+{
+ int last_err= GetLastError();
+ va_list args;
+ fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ if (last_err)
+ {
+ char *message_text;
+ if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
+ |FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_err , 0, (LPSTR)&message_text,
+ 0, NULL))
+ {
+ fprintf(stderr,"error: %d, %s\n",last_err, message_text);
+ LocalFree(message_text);
+ }
+ else
+ {
+ /* FormatMessage failed, print error code only */
+ fprintf(stderr,"error:%d\n", last_err);
+ }
+ }
+ fflush(stderr);
+ exit(1);
+}
+
+
+DWORD get_parent_pid(DWORD pid)
+{
+ HANDLE snapshot;
+ DWORD parent_pid= 0;
+ PROCESSENTRY32 pe32;
+ pe32.dwSize= sizeof(PROCESSENTRY32);
+
+ snapshot= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (snapshot == INVALID_HANDLE_VALUE)
+ die("CreateToolhelp32Snapshot failed");
+
+ if (!Process32First(snapshot, &pe32))
+ {
+ CloseHandle(snapshot);
+ die("Process32First failed");
+ }
+
+ do
+ {
+ if (pe32.th32ProcessID == pid)
+ parent_pid= pe32.th32ParentProcessID;
+ } while(Process32Next( snapshot, &pe32));
+ CloseHandle(snapshot);
+
+ if (parent_pid == 0)
+ die("Could not find parent pid");
+
+ return parent_pid;
+}
+
+
+enum {
+ PARENT,
+ CHILD,
+ EVENT,
+ NUM_HANDLES
+};
+
+
+HANDLE shutdown_event;
+void handle_signal (int signal)
+{
+ message("Got signal: %d", signal);
+ if(SetEvent(shutdown_event) == 0) {
+ /* exit safe_process and (hopefully) kill off the child */
+ die("Failed to SetEvent");
+ }
+}
+
+
+int main(int argc, const char** argv )
+{
+ char child_args[4096]= {0};
+ DWORD pid= GetCurrentProcessId();
+ DWORD parent_pid= get_parent_pid(pid);
+ HANDLE job_handle;
+ HANDLE wait_handles[NUM_HANDLES]= {0};
+ PROCESS_INFORMATION process_info= {0};
+ BOOL nocore= FALSE;
+
+ sprintf(safe_process_name, "safe_process[%lu]", pid);
+
+ /* Create an event for the signal handler */
+ if ((shutdown_event=
+ CreateEvent(NULL, TRUE, FALSE, safe_process_name)) == NULL)
+ die("Failed to create shutdown_event");
+ wait_handles[EVENT]= shutdown_event;
+
+ signal(SIGINT, handle_signal);
+ signal(SIGBREAK, handle_signal);
+ signal(SIGTERM, handle_signal);
+
+ message("Started");
+
+ /* Parse arguments */
+ for (int i= 1; i < argc; i++) {
+ const char* arg= argv[i];
+ char* to= child_args;
+ if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
+ /* Got the "--" delimiter */
+ if (i >= argc)
+ die("No real args -> nothing to do");
+ /* Copy the remaining args to child_arg */
+ for (int j= i+1; j < argc; j++) {
+ arg= argv[j];
+ if (strchr (arg, ' ') &&
+ arg[0] != '\"' &&
+ arg[strlen(arg)] != '\"')
+ {
+ /* Quote arg that contains spaces and are not quoted already */
+ to+= _snprintf(to, child_args + sizeof(child_args) - to,
+ "\"%s\" ", arg);
+ }
+ else
+ {
+ to+= _snprintf(to, child_args + sizeof(child_args) - to,
+ "%s ", arg);
+ }
+ }
+ break;
+ } else {
+ if (strcmp(arg, "--verbose") == 0)
+ verbose++;
+ else if (strncmp(arg, "--parent-pid", 12) == 0)
+ {
+ /* Override parent_pid with a value provided by user */
+ const char* start;
+ if ((start= strstr(arg, "=")) == NULL)
+ die("Could not find start of option value in '%s'", arg);
+ start++; /* Step past = */
+ if ((parent_pid= atoi(start)) == 0)
+ die("Invalid value '%s' passed to --parent-id", start);
+ }
+ else if (strcmp(arg, "--nocore") == 0)
+ {
+ nocore= TRUE;
+ }
+ else if ( strncmp (arg, "--env ", 6) == 0 )
+ {
+ putenv(strdup(arg+6));
+ }
+ else
+ die("Unknown option: %s", arg);
+ }
+ }
+ if (*child_args == '\0')
+ die("nothing to do");
+
+ /* Open a handle to the parent process */
+ message("parent_pid: %d", parent_pid);
+ if (parent_pid == pid)
+ die("parent_pid is equal to own pid!");
+
+ if ((wait_handles[PARENT]=
+ OpenProcess(SYNCHRONIZE, FALSE, parent_pid)) == NULL)
+ die("Failed to open parent process with pid: %d", parent_pid);
+
+ /* Create the child process in a job */
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ STARTUPINFO si = { 0 };
+ si.cb = sizeof(si);
+
+ /*
+ Create the job object to make it possible to kill the process
+ and all of it's children in one go
+ */
+ if ((job_handle= CreateJobObject(NULL, NULL)) == NULL)
+ die("CreateJobObject failed");
+
+ /*
+ Make all processes associated with the job terminate when the
+ last handle to the job is closed.
+ */
+#ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
+#define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000
+#endif
+
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation,
+ &jeli, sizeof(jeli)) == 0)
+ message("SetInformationJobObject failed, continue anyway...");
+
+ /* Avoid popup box */
+ if (nocore)
+ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX
+ | SEM_NOOPENFILEERRORBOX);
+
+#if 0
+ /* Setup stdin, stdout and stderr redirect */
+ si.dwFlags= STARTF_USESTDHANDLES;
+ si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
+ si.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE);
+ si.hStdError= GetStdHandle(STD_ERROR_HANDLE);
+#endif
+
+ /*
+ Create the process suspended to make sure it's assigned to the
+ Job before it creates any process of it's own
+
+ Allow the new process to break away from any job that this
+ process is part of so that it can be assigned to the new JobObject
+ we just created. This is safe since the new JobObject is created with
+ the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag, making sure it will be
+ terminated when the last handle to it is closed(which is owned by
+ this process).
+
+ If breakaway from job fails on some reason, fallback is to create a
+ new process group. Process groups also allow to kill process and its
+ descedants, subject to some restrictions (processes have to run within
+ the same console,and must not ignore CTRL_BREAK)
+ */
+ DWORD create_flags[]= {CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP, 0};
+ BOOL process_created= FALSE;
+ BOOL jobobject_assigned= FALSE;
+
+ for (size_t i=0; i < sizeof(create_flags)/sizeof(create_flags[0]); i++)
+ {
+ process_created= CreateProcess(NULL, (LPSTR)child_args,
+ NULL,
+ NULL,
+ TRUE, /* inherit handles */
+ CREATE_SUSPENDED | create_flags[i],
+ NULL,
+ NULL,
+ &si,
+ &process_info);
+ if (process_created)
+ {
+ jobobject_assigned= AssignProcessToJobObject(job_handle, process_info.hProcess);
+ break;
+ }
+ }
+
+ if (!process_created)
+ {
+ die("CreateProcess failed");
+ }
+ ResumeThread(process_info.hThread);
+ CloseHandle(process_info.hThread);
+
+ wait_handles[CHILD]= process_info.hProcess;
+
+ message("Started child %d", process_info.dwProcessId);
+
+ /* Monitor loop */
+ DWORD child_exit_code= 1;
+ DWORD wait_res= WaitForMultipleObjects(NUM_HANDLES, wait_handles,
+ FALSE, INFINITE);
+ switch (wait_res)
+ {
+ case WAIT_OBJECT_0 + PARENT:
+ message("Parent exit");
+ break;
+ case WAIT_OBJECT_0 + CHILD:
+ if (GetExitCodeProcess(wait_handles[CHILD], &child_exit_code) == 0)
+ message("Child exit: could not get exit_code");
+ else
+ message("Child exit: exit_code: %d", child_exit_code);
+ break;
+ case WAIT_OBJECT_0 + EVENT:
+ message("Wake up from shutdown_event");
+ break;
+
+ default:
+ message("Unexpected result %d from WaitForMultipleObjects", wait_res);
+ break;
+ }
+ message("Exiting, child: %d", process_info.dwProcessId);
+
+ if (TerminateJobObject(job_handle, 201) == 0)
+ message("TerminateJobObject failed");
+ CloseHandle(job_handle);
+ message("Job terminated and closed");
+
+
+
+ if (wait_res != WAIT_OBJECT_0 + CHILD)
+ {
+ if (!jobobject_assigned)
+ {
+ TerminateProcess(process_info.hProcess, 202);
+ }
+ /* The child has not yet returned, wait for it */
+ message("waiting for child to exit");
+ if ((wait_res= WaitForSingleObject(wait_handles[CHILD], INFINITE))
+ != WAIT_OBJECT_0)
+ {
+ message("child wait failed: %d", wait_res);
+ }
+ else
+ {
+ message("child wait succeeded");
+ }
+ /* Child's exit code should now be 201, no need to get it */
+ }
+
+ message("Closing handles");
+ for (int i= 0; i < NUM_HANDLES; i++)
+ CloseHandle(wait_handles[i]);
+
+ message("Exiting, exit_code: %d", child_exit_code);
+ exit(child_exit_code);
+}
+
diff --git a/mysql-test/lib/My/SafeProcess/wsrep_check_version.c b/mysql-test/lib/My/SafeProcess/wsrep_check_version.c
new file mode 100644
index 00000000..7398bc8a
--- /dev/null
+++ b/mysql-test/lib/My/SafeProcess/wsrep_check_version.c
@@ -0,0 +1,51 @@
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include <my_global.h>
+#include <string.h>
+#include "../../../../wsrep-lib/wsrep-API/v26/wsrep_api.h"
+
+int main(int argc, char **argv)
+{
+ int rc= 1;
+ void *dlh;
+ const char *provider= getenv("WSREP_PROVIDER");
+ char** dlversion= NULL;
+
+ if (!provider || !*provider)
+ {
+ printf("WSREP_PROVIDER is not set\n");
+ return 1;
+ }
+ if (!(dlh= dlopen(provider, RTLD_NOW | RTLD_LOCAL)))
+ {
+ printf("Can't open WSREP_PROVIDER (%s) library, error: %s\n",
+ provider, dlerror());
+ return 1;
+ }
+
+ dlversion= (char**) dlsym(dlh, "wsrep_interface_version");
+ if (dlversion && *dlversion)
+ {
+ rc= strcmp(*dlversion, WSREP_INTERFACE_VERSION) ? 2 : 0;
+ if (rc)
+ printf("Wrong wsrep provider library version, found: %s, need: %s\n", *dlversion, WSREP_INTERFACE_VERSION);
+ }
+ else
+ printf("Galera library does not contain a version symbol");
+
+ dlclose(dlh);
+ return rc;
+}