MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
safe_process_win.cc
1 /* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
2 
3  This program is free software; you can redistribute it and/or modify
4  it under the terms of the GNU General Public License as published by
5  the Free Software Foundation; version 2 of the License.
6 
7  This program is distributed in the hope that it will be useful,
8  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  GNU General Public License for more details.
11 
12  You should have received a copy of the GNU General Public License
13  along with this program; if not, write to the Free Software
14  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
15 
16 
17 /*
18  Utility program that encapsulates process creation, monitoring
19  and bulletproof process cleanup
20 
21  Usage:
22  safe_process [options to safe_process] -- progname arg1 ... argn
23 
24  To safeguard mysqld you would invoke safe_process with a few options
25  for safe_process itself followed by a double dash to indicate start
26  of the command line for the program you really want to start
27 
28  $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
29 
30  This would redirect output to output.log and then start mysqld,
31  once it has done that it will continue to monitor the child as well
32  as the parent.
33 
34  The safe_process then checks the follwing things:
35  1. Child exits, propagate the childs return code to the parent
36  by exiting with the same return code as the child.
37 
38  2. Parent dies, immediately kill the child and exit, thus the
39  parent does not need to properly cleanup any child, it is handled
40  automatically.
41 
42  3. Signal's recieced by the process will trigger same action as 2)
43 
44  4. The named event "safe_process[pid]" can be signaled and will
45  trigger same action as 2)
46 
47  WARNING! Be careful when using ProcessExplorer, since it will open
48  a handle to each process(and maybe also the Job), the process
49  spawned by safe_process will not be closed off when safe_process
50  is killed.
51 */
52 
53 #include <windows.h>
54 #include <stdio.h>
55 #include <tlhelp32.h>
56 #include <signal.h>
57 #include <stdlib.h>
58 
59 static int verbose= 0;
60 static char safe_process_name[32]= {0};
61 
62 static void message(const char* fmt, ...)
63 {
64  if (!verbose)
65  return;
66  va_list args;
67  fprintf(stderr, "%s: ", safe_process_name);
68  va_start(args, fmt);
69  vfprintf(stderr, fmt, args);
70  fprintf(stderr, "\n");
71  va_end(args);
72  fflush(stderr);
73 }
74 
75 
76 static void die(const char* fmt, ...)
77 {
78  DWORD last_err= GetLastError();
79  va_list args;
80  fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
81  va_start(args, fmt);
82  vfprintf(stderr, fmt, args);
83  fprintf(stderr, "\n");
84  va_end(args);
85  if (last_err)
86  {
87  char *message_text;
88  if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
89  |FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_err , 0, (LPSTR)&message_text,
90  0, NULL))
91  {
92  fprintf(stderr,"error: %d, %s\n",last_err, message_text);
93  LocalFree(message_text);
94  }
95  else
96  {
97  /* FormatMessage failed, print error code only */
98  fprintf(stderr,"error:%d\n", last_err);
99  }
100  }
101  fflush(stderr);
102  exit(1);
103 }
104 
105 
106 DWORD get_parent_pid(DWORD pid)
107 {
108  HANDLE snapshot;
109  DWORD parent_pid= -1;
110  PROCESSENTRY32 pe32;
111  pe32.dwSize= sizeof(PROCESSENTRY32);
112 
113  snapshot= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
114  if (snapshot == INVALID_HANDLE_VALUE)
115  die("CreateToolhelp32Snapshot failed");
116 
117  if (!Process32First(snapshot, &pe32))
118  {
119  CloseHandle(snapshot);
120  die("Process32First failed");
121  }
122 
123  do
124  {
125  if (pe32.th32ProcessID == pid)
126  parent_pid= pe32.th32ParentProcessID;
127  } while(Process32Next( snapshot, &pe32));
128  CloseHandle(snapshot);
129 
130  if (parent_pid == -1)
131  die("Could not find parent pid");
132 
133  return parent_pid;
134 }
135 
136 
137 enum {
138  PARENT,
139  CHILD,
140  EVENT,
141  NUM_HANDLES
142 };
143 
144 
145 HANDLE shutdown_event;
146 void handle_signal (int signal)
147 {
148  message("Got signal: %d", signal);
149  if(SetEvent(shutdown_event) == 0) {
150  /* exit safe_process and (hopefully) kill off the child */
151  die("Failed to SetEvent");
152  }
153 }
154 
155 
156 int main(int argc, const char** argv )
157 {
158  char child_args[4096]= {0};
159  DWORD pid= GetCurrentProcessId();
160  DWORD parent_pid= get_parent_pid(pid);
161  HANDLE job_handle;
162  HANDLE wait_handles[NUM_HANDLES]= {0};
163  PROCESS_INFORMATION process_info= {0};
164  BOOL nocore= FALSE;
165 
166  sprintf(safe_process_name, "safe_process[%d]", pid);
167 
168  /* Create an event for the signal handler */
169  if ((shutdown_event=
170  CreateEvent(NULL, TRUE, FALSE, safe_process_name)) == NULL)
171  die("Failed to create shutdown_event");
172  wait_handles[EVENT]= shutdown_event;
173 
174  signal(SIGINT, handle_signal);
175  signal(SIGBREAK, handle_signal);
176  signal(SIGTERM, handle_signal);
177 
178  message("Started");
179 
180  /* Parse arguments */
181  for (int i= 1; i < argc; i++) {
182  const char* arg= argv[i];
183  char* to= child_args;
184  if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
185  /* Got the "--" delimiter */
186  if (i >= argc)
187  die("No real args -> nothing to do");
188  /* Copy the remaining args to child_arg */
189  for (int j= i+1; j < argc; j++) {
190  arg= argv[j];
191  if (strchr (arg, ' ') &&
192  arg[0] != '\"' &&
193  arg[strlen(arg)] != '\"')
194  {
195  /* Quote arg that contains spaces and are not quoted already */
196  to+= _snprintf(to, child_args + sizeof(child_args) - to,
197  "\"%s\" ", arg);
198  }
199  else
200  {
201  to+= _snprintf(to, child_args + sizeof(child_args) - to,
202  "%s ", arg);
203  }
204  }
205  break;
206  } else {
207  if (strcmp(arg, "--verbose") == 0)
208  verbose++;
209  else if (strncmp(arg, "--parent-pid", 10) == 0)
210  {
211  /* Override parent_pid with a value provided by user */
212  const char* start;
213  if ((start= strstr(arg, "=")) == NULL)
214  die("Could not find start of option value in '%s'", arg);
215  start++; /* Step past = */
216  if ((parent_pid= atoi(start)) == 0)
217  die("Invalid value '%s' passed to --parent-id", start);
218  }
219  else if (strcmp(arg, "--nocore") == 0)
220  {
221  nocore= TRUE;
222  }
223  else if ( strncmp (arg, "--env ", 6) == 0 )
224  {
225  putenv(strdup(arg+6));
226  }
227  else
228  die("Unknown option: %s", arg);
229  }
230  }
231  if (*child_args == '\0')
232  die("nothing to do");
233 
234  /* Open a handle to the parent process */
235  message("parent_pid: %d", parent_pid);
236  if (parent_pid == pid)
237  die("parent_pid is equal to own pid!");
238 
239  if ((wait_handles[PARENT]=
240  OpenProcess(SYNCHRONIZE, FALSE, parent_pid)) == NULL)
241  die("Failed to open parent process with pid: %d", parent_pid);
242 
243  /* Create the child process in a job */
244  JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
245  STARTUPINFO si = { 0 };
246  si.cb = sizeof(si);
247 
248  /*
249  Create the job object to make it possible to kill the process
250  and all of it's children in one go
251  */
252  if ((job_handle= CreateJobObject(NULL, NULL)) == NULL)
253  die("CreateJobObject failed");
254 
255  /*
256  Make all processes associated with the job terminate when the
257  last handle to the job is closed.
258  */
259 #ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
260 #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000
261 #endif
262 
263  jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
264  if (SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation,
265  &jeli, sizeof(jeli)) == 0)
266  message("SetInformationJobObject failed, continue anyway...");
267 
268  /* Avoid popup box */
269  if (nocore)
270  SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX
271  | SEM_NOOPENFILEERRORBOX);
272 
273 #if 0
274  /* Setup stdin, stdout and stderr redirect */
275  si.dwFlags= STARTF_USESTDHANDLES;
276  si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
277  si.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE);
278  si.hStdError= GetStdHandle(STD_ERROR_HANDLE);
279 #endif
280 
281  /*
282  Create the process suspended to make sure it's assigned to the
283  Job before it creates any process of it's own
284 
285  Allow the new process to break away from any job that this
286  process is part of so that it can be assigned to the new JobObject
287  we just created. This is safe since the new JobObject is created with
288  the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag, making sure it will be
289  terminated when the last handle to it is closed(which is owned by
290  this process).
291 
292  If breakaway from job fails on some reason, fallback is to create a
293  new process group. Process groups also allow to kill process and its
294  descedants, subject to some restrictions (processes have to run within
295  the same console,and must not ignore CTRL_BREAK)
296  */
297  DWORD create_flags[]= {CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP, 0};
298  BOOL process_created= FALSE;
299  BOOL jobobject_assigned= FALSE;
300 
301  for (int i=0; i < sizeof(create_flags)/sizeof(create_flags[0]); i++)
302  {
303  process_created= CreateProcess(NULL, (LPSTR)child_args,
304  NULL,
305  NULL,
306  TRUE, /* inherit handles */
307  CREATE_SUSPENDED | create_flags[i],
308  NULL,
309  NULL,
310  &si,
311  &process_info);
312  if (process_created)
313  {
314  jobobject_assigned= AssignProcessToJobObject(job_handle, process_info.hProcess);
315  break;
316  }
317  }
318 
319  if (!process_created)
320  {
321  die("CreateProcess failed");
322  }
323  ResumeThread(process_info.hThread);
324  CloseHandle(process_info.hThread);
325 
326  wait_handles[CHILD]= process_info.hProcess;
327 
328  message("Started child %d", process_info.dwProcessId);
329 
330  /* Monitor loop */
331  DWORD child_exit_code= 1;
332  DWORD wait_res= WaitForMultipleObjects(NUM_HANDLES, wait_handles,
333  FALSE, INFINITE);
334  switch (wait_res)
335  {
336  case WAIT_OBJECT_0 + PARENT:
337  message("Parent exit");
338  break;
339  case WAIT_OBJECT_0 + CHILD:
340  if (GetExitCodeProcess(wait_handles[CHILD], &child_exit_code) == 0)
341  message("Child exit: could not get exit_code");
342  else
343  message("Child exit: exit_code: %d", child_exit_code);
344  break;
345  case WAIT_OBJECT_0 + EVENT:
346  message("Wake up from shutdown_event");
347  break;
348 
349  default:
350  message("Unexpected result %d from WaitForMultipleObjects", wait_res);
351  break;
352  }
353  message("Exiting, child: %d", process_info.dwProcessId);
354 
355  if (TerminateJobObject(job_handle, 201) == 0)
356  message("TerminateJobObject failed");
357  CloseHandle(job_handle);
358  message("Job terminated and closed");
359 
360  if (!jobobject_assigned)
361  {
362  GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, process_info.dwProcessId);
363  TerminateProcess(process_info.hProcess, 202);
364  }
365 
366  if (wait_res != WAIT_OBJECT_0 + CHILD)
367  {
368  /* The child has not yet returned, wait for it */
369  message("waiting for child to exit");
370  if ((wait_res= WaitForSingleObject(wait_handles[CHILD], INFINITE))
371  != WAIT_OBJECT_0)
372  {
373  message("child wait failed: %d", wait_res);
374  }
375  else
376  {
377  message("child wait succeeded");
378  }
379  /* Child's exit code should now be 201, no need to get it */
380  }
381 
382  message("Closing handles");
383  for (int i= 0; i < NUM_HANDLES; i++)
384  CloseHandle(wait_handles[i]);
385 
386  message("Exiting, exit_code: %d", child_exit_code);
387  exit(child_exit_code);
388 }
389