1/* Copyright (c) 2008, 2012, Oracle and/or its affiliates
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 Street, 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*/
45
46#include <sys/types.h>
47#include <sys/wait.h>
48#include <sys/time.h>
49#include <sys/resource.h>
50#include <unistd.h>
51#include <stdarg.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <signal.h>
55#include <string.h>
56#include <errno.h>
57
58int verbose= 0;
59int terminated= 0;
60pid_t child_pid= -1;
61char safe_process_name[32]= {0};
62
63
64static void message(const char* fmt, ...)
65{
66 if (!verbose)
67 return;
68 va_list args;
69 fprintf(stderr, "%s: ", safe_process_name);
70 va_start(args, fmt);
71 vfprintf(stderr, fmt, args);
72 fprintf(stderr, "\n");
73 va_end(args);
74 fflush(stderr);
75}
76
77
78static void die(const char* fmt, ...)
79{
80 va_list args;
81 fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
82 va_start(args, fmt);
83 vfprintf(stderr, fmt, args);
84 fprintf(stderr, "\n");
85 va_end(args);
86 if (int last_err= errno)
87 fprintf(stderr, "error: %d, %s\n", last_err, strerror(last_err));
88 exit(6);
89}
90
91
92#ifdef __APPLE__
93#include <sys/sysctl.h>
94
95
96/*
97 Eventually we may want to adopt kern.corefile parsing code from
98 https://opensource.apple.com/source/xnu/xnu-3247.1.106/bsd/kern/kern_proc.c
99*/
100
101void handle_core(pid_t pid)
102{
103 char corefile[256];
104 int coredump;
105 size_t corefile_size= sizeof(corefile);
106 size_t coredump_size= sizeof(coredump);
107
108 if (sysctlbyname("kern.coredump", &coredump, &coredump_size, 0, 0) ||
109 sysctlbyname("kern.corefile", corefile, &corefile_size, 0, 0))
110 {
111 message("sysctlbyname failed: %d (%s)", errno, strerror(errno));
112 return;
113 }
114
115 if (!coredump)
116 {
117 message("core dumps disabled, to enable run sudo sysctl kern.coredump=1");
118 return;
119 }
120
121 if (!strncmp(corefile, "/cores/core.%P", corefile_size))
122 {
123 char from[256];
124 char *to= from + 7;
125
126 snprintf(from, sizeof(from), "/cores/core.%u", pid);
127 if (!access(from, R_OK))
128 {
129 if (symlink(from, to))
130 message("symlink failed: %d (%s)", errno, strerror(errno));
131 }
132 }
133}
134#else
135void handle_core(pid_t pid __attribute__((unused))) {}
136#endif
137
138
139static int kill_child(bool was_killed)
140{
141 int status= 0;
142
143 message("Killing child: %d", child_pid);
144 // Terminate whole process group
145 if (! was_killed)
146 kill(-child_pid, SIGKILL);
147
148 pid_t ret_pid= waitpid(child_pid, &status, 0);
149 if (ret_pid == child_pid)
150 {
151 int exit_code= 1;
152 if (WIFEXITED(status))
153 {
154 // Process has exited, collect return status
155 exit_code= WEXITSTATUS(status);
156 message("Child exit: %d", exit_code);
157 // Exit with exit status of the child
158 return exit_code;
159 }
160
161 if (WIFSIGNALED(status))
162 {
163 message("Child killed by signal: %d", WTERMSIG(status));
164 handle_core(child_pid);
165 }
166
167 return exit_code;
168 }
169 return 5;
170}
171
172
173extern "C" void handle_abort(int sig)
174{
175 message("Got signal %d, child_pid: %d, sending ABRT", sig, child_pid);
176
177 if (child_pid > 0) {
178 kill(-child_pid, SIGABRT); // Don't wait for it to terminate
179 }
180}
181
182
183extern "C" void handle_signal(int sig)
184{
185 message("Got signal %d, child_pid: %d", sig, child_pid);
186 terminated= 1;
187
188 if (child_pid > 0)
189 _exit(kill_child(sig == SIGCHLD));
190
191 // Ignore further signals
192 signal(SIGTERM, SIG_IGN);
193 signal(SIGINT, SIG_IGN);
194 signal(SIGHUP, SIG_IGN);
195
196 // Continune execution, allow the child to be started and
197 // finally terminated by monitor loop
198}
199
200
201void setlimit(int what, uint soft, uint hard)
202{
203 struct rlimit lim = { soft, hard };
204 if (setrlimit (what, &lim) < 0)
205 message("setrlimit failed, errno=%d", errno);
206}
207
208
209int main(int argc, char* const argv[] )
210{
211 char* const* child_argv= 0;
212 pid_t own_pid= getpid();
213 pid_t parent_pid= getppid();
214 bool nocore = false;
215 struct sigaction sa,sa_abort;
216
217 sa.sa_handler= handle_signal;
218 sa.sa_flags= SA_NOCLDSTOP;
219 sigemptyset(&sa.sa_mask);
220
221 sa_abort.sa_handler= handle_abort;
222 sigemptyset(&sa_abort.sa_mask);
223 /* Install signal handlers */
224 sigaction(SIGTERM, &sa,NULL);
225 sigaction(SIGINT, &sa,NULL);
226 sigaction(SIGHUP, &sa, NULL);
227 sigaction(SIGCHLD, &sa,NULL);
228 sigaction(SIGABRT, &sa_abort,NULL);
229
230 sprintf(safe_process_name, "safe_process[%ld]", (long) own_pid);
231
232 message("Started");
233
234 /* Parse arguments */
235 for (int i= 1; i < argc; i++) {
236 const char* arg= argv[i];
237 if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
238 /* Got the "--" delimiter */
239 if (i >= argc)
240 die("No real args -> nothing to do");
241 child_argv= &argv[i+1];
242 break;
243 } else {
244 if ( strcmp(arg, "--verbose") == 0 )
245 verbose++;
246 else if ( strncmp(arg, "--parent-pid", 12) == 0 )
247 {
248 /* Override parent_pid with a value provided by user */
249 const char* start;
250 if ((start= strstr(arg, "=")) == NULL)
251 die("Could not find start of option value in '%s'", arg);
252 start++; /* Step past = */
253 if ((parent_pid= atoi(start)) == 0)
254 die("Invalid value '%s' passed to --parent-id", start);
255 }
256 else if ( strcmp(arg, "--nocore") == 0 )
257 {
258 nocore = true; // Don't allow the process to dump core
259 }
260 else if ( strncmp (arg, "--env ", 6) == 0 )
261 {
262 putenv(strdup(arg+6));
263 }
264 else
265 die("Unknown option: %s", arg);
266 }
267 }
268 if (!child_argv || *child_argv == 0)
269 die("nothing to do");
270
271 message("parent_pid: %d", parent_pid);
272 if (parent_pid == own_pid)
273 die("parent_pid is equal to own pid!");
274
275 char buf;
276 int pfd[2];
277 if (pipe(pfd) == -1)
278 die("Failed to create pipe");
279
280 /* Create the child process */
281 while((child_pid= fork()) == -1)
282 {
283 message("fork failed");
284 sleep(1);
285 }
286
287 /*
288 Child: Make this process it's own process group to be able to kill
289 it and any its children that hasn't changed a group themselves)
290
291 Parent: Detach from the parent's process group, so that killing a parent
292 group wouldn't kill us (if we're killed, there's no one to kill our child
293 processes that run in their own process group). There's a loop below
294 that monitors the parent, it's enough.
295 */
296 setpgid(0, 0);
297
298
299 if (child_pid == 0)
300 {
301 close(pfd[0]); // Close unused read end
302
303 // Use default signal handlers in child
304 signal(SIGTERM, SIG_DFL);
305 signal(SIGINT, SIG_DFL);
306 signal(SIGHUP, SIG_DFL);
307 signal(SIGCHLD, SIG_DFL);
308
309 if (nocore)
310 setlimit(RLIMIT_CORE, 0, 0);
311
312 /*
313 mysqld defaults depend on that. make test results stable and independent
314 from the environment
315 */
316 setlimit(RLIMIT_NOFILE, 1024, 1024);
317
318 // Signal that child is ready
319 buf= 37;
320 if ((write(pfd[1], &buf, 1)) < 1)
321 die("Failed to signal that child is ready");
322 // Close write end
323 close(pfd[1]);
324
325 execvp(child_argv[0], child_argv);
326 die("Failed to exec child");
327 }
328
329 close(pfd[1]); // Close unused write end
330
331 // Wait for child to signal it's ready
332 if ((read(pfd[0], &buf, 1)) < 1)
333 die("Failed to read signal from child");
334
335 if (buf != 37)
336 die("Didn't get 37 from pipe");
337 close(pfd[0]); // Close read end
338
339 /* Monitor loop */
340 message("Started child %d, terminated: %d", child_pid, terminated);
341
342 while (!terminated)
343 {
344 // Check if parent is still alive
345 if (kill(parent_pid, 0) != 0)
346 {
347 message("Parent is not alive anymore");
348 break;
349 }
350 /* Wait for parent or child to die */
351 sleep(1);
352 }
353 return kill_child(0);
354}
355
356