365 lines
12 KiB
C++
365 lines
12 KiB
C++
// Copyright (c) 2000, 2018, 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, version 2.0,
|
|
// as published by the Free Software Foundation.
|
|
//
|
|
// This program is also distributed with certain software (including
|
|
// but not limited to OpenSSL) that is licensed under separate terms,
|
|
// as designated in a particular file or component or in included license
|
|
// documentation. The authors of MySQL hereby grant you an additional
|
|
// permission to link the program and your derivative works with the
|
|
// separately licensed software that they have included with MySQL.
|
|
//
|
|
// 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, version 2.0, 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-1301 USA.
|
|
|
|
/// @file safe_process_win.cc
|
|
///
|
|
/// 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 <signal.h>
|
|
#include <windows.h>
|
|
|
|
// Needs to be included after <windows.h>.
|
|
#include <tlhelp32.h>
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
enum { PARENT, CHILD, EVENT, NUM_HANDLES };
|
|
HANDLE shutdown_event;
|
|
|
|
static char safe_process_name[32];
|
|
static int verbose = 0;
|
|
|
|
static void message(const char *fmt, ...) {
|
|
if (!verbose) return;
|
|
va_list args;
|
|
std::fprintf(stderr, "%s: ", safe_process_name);
|
|
va_start(args, fmt);
|
|
std::vfprintf(stderr, fmt, args);
|
|
std::fprintf(stderr, "\n");
|
|
va_end(args);
|
|
fflush(stderr);
|
|
}
|
|
|
|
static void die(const char *fmt, ...) {
|
|
DWORD last_err = GetLastError();
|
|
|
|
va_list args;
|
|
std::fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
|
|
va_start(args, fmt);
|
|
std::vfprintf(stderr, fmt, args);
|
|
std::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)) {
|
|
std::fprintf(stderr, "error: %d, %s\n", last_err, message_text);
|
|
LocalFree(message_text);
|
|
} else {
|
|
// FormatMessage failed, print error code only
|
|
std::fprintf(stderr, "error:%d\n", last_err);
|
|
}
|
|
}
|
|
|
|
fflush(stderr);
|
|
std::exit(1);
|
|
}
|
|
|
|
DWORD get_parent_pid(DWORD pid) {
|
|
PROCESSENTRY32 pe32;
|
|
pe32.dwSize = sizeof(PROCESSENTRY32);
|
|
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (snapshot == INVALID_HANDLE_VALUE) die("CreateToolhelp32Snapshot failed");
|
|
|
|
if (!Process32First(snapshot, &pe32)) {
|
|
CloseHandle(snapshot);
|
|
die("Process32First failed");
|
|
}
|
|
|
|
DWORD parent_pid = -1;
|
|
do {
|
|
if (pe32.th32ProcessID == pid) parent_pid = pe32.th32ParentProcessID;
|
|
} while (Process32Next(snapshot, &pe32));
|
|
|
|
CloseHandle(snapshot);
|
|
|
|
if (parent_pid == -1) die("Could not find parent pid");
|
|
|
|
return parent_pid;
|
|
}
|
|
|
|
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) {
|
|
DWORD pid = GetCurrentProcessId();
|
|
sprintf(safe_process_name, "safe_process[%d]", 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");
|
|
|
|
HANDLE wait_handles[NUM_HANDLES] = {0};
|
|
wait_handles[EVENT] = shutdown_event;
|
|
|
|
signal(SIGINT, handle_signal);
|
|
signal(SIGBREAK, handle_signal);
|
|
signal(SIGTERM, handle_signal);
|
|
|
|
message("Started");
|
|
|
|
BOOL nocore = FALSE;
|
|
DWORD parent_pid = get_parent_pid(pid);
|
|
char child_args[8192] = {0};
|
|
std::string exe_name;
|
|
|
|
// 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 += std::snprintf(to, child_args + sizeof(child_args) - to,
|
|
"\"%s\" ", arg);
|
|
} else {
|
|
to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
|
|
arg);
|
|
}
|
|
}
|
|
|
|
// Check if executable is mysqltest.exe client
|
|
if (exe_name.compare("mysqltest") == 0) {
|
|
char safe_process_pid[32];
|
|
|
|
// Pass safeprocess PID to mysqltest which is used to create an event
|
|
std::sprintf(safe_process_pid, "--safe-process-pid=%d", pid);
|
|
to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
|
|
safe_process_pid);
|
|
}
|
|
break;
|
|
} else {
|
|
if (strcmp(arg, "--verbose") == 0)
|
|
verbose++;
|
|
else if (strncmp(arg, "--parent-pid", 10) == 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);
|
|
// Step past '='
|
|
start++;
|
|
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 if (std::strncmp(arg, "--safe-process-name", 19) == 0) {
|
|
exe_name = arg + 20;
|
|
} 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.
|
|
HANDLE job_handle;
|
|
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 jobobject_assigned = FALSE;
|
|
BOOL process_created = FALSE;
|
|
PROCESS_INFORMATION process_info = {0};
|
|
|
|
for (int 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 (!jobobject_assigned) {
|
|
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, process_info.dwProcessId);
|
|
TerminateProcess(process_info.hProcess, 202);
|
|
}
|
|
|
|
if (wait_res != WAIT_OBJECT_0 + CHILD) {
|
|
// 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);
|
|
std::exit(child_exit_code);
|
|
}
|