/src/haproxy/src/mworker.c
Line | Count | Source |
1 | | /* |
2 | | * Master Worker |
3 | | * |
4 | | * Copyright HAProxy Technologies 2019 - William Lallemand <wlallemand@haproxy.com> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #define _GNU_SOURCE |
14 | | |
15 | | #include <errno.h> |
16 | | #include <fcntl.h> |
17 | | #include <signal.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | #include <sys/wait.h> |
21 | | #include <unistd.h> |
22 | | |
23 | | #include <haproxy/api.h> |
24 | | #include <haproxy/cfgparse.h> |
25 | | #include <haproxy/cli.h> |
26 | | #include <haproxy/errors.h> |
27 | | #include <haproxy/fd.h> |
28 | | #include <haproxy/global.h> |
29 | | #include <haproxy/log.h> |
30 | | #include <haproxy/list.h> |
31 | | #include <haproxy/listener.h> |
32 | | #include <haproxy/mworker.h> |
33 | | #include <haproxy/peers.h> |
34 | | #include <haproxy/proto_sockpair.h> |
35 | | #include <haproxy/proxy.h> |
36 | | #include <haproxy/ring.h> |
37 | | #include <haproxy/sc_strm.h> |
38 | | #include <haproxy/signal.h> |
39 | | #include <haproxy/ssl_sock.h> |
40 | | #include <haproxy/stconn.h> |
41 | | #include <haproxy/stream.h> |
42 | | #include <haproxy/systemd.h> |
43 | | #include <haproxy/tools.h> |
44 | | #include <haproxy/version.h> |
45 | | |
46 | | |
47 | | static int exitcode = -1; |
48 | | int max_reloads = 50; /* max number of reloads a worker can have until they are killed */ |
49 | | int load_status; /* worker process startup status: 1 - loaded successfully; 0 - load failed */ |
50 | | struct mworker_proc *proc_self = NULL; /* process structure of current process */ |
51 | | struct list mworker_cli_conf = LIST_HEAD_INIT(mworker_cli_conf); /* master CLI configuration (-S flag) */ |
52 | | |
53 | | /* ----- children processes handling ----- */ |
54 | | |
55 | | /* |
56 | | * Send signal to every known children. |
57 | | */ |
58 | | |
59 | | static void mworker_kill(int sig) |
60 | 0 | { |
61 | 0 | struct mworker_proc *child; |
62 | |
|
63 | 0 | list_for_each_entry(child, &proc_list, list) { |
64 | | /* careful there, we must be sure that the pid > 0, we don't want to emit a kill -1 */ |
65 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->pid > 0)) |
66 | 0 | kill(child->pid, sig); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | void mworker_kill_max_reloads(int sig) |
71 | 0 | { |
72 | 0 | struct mworker_proc *child; |
73 | |
|
74 | 0 | list_for_each_entry(child, &proc_list, list) { |
75 | 0 | if (max_reloads != -1 && (child->options & PROC_O_TYPE_WORKER) && |
76 | 0 | (child->pid > 0) && (child->reloads > max_reloads)) |
77 | 0 | kill(child->pid, sig); |
78 | 0 | } |
79 | 0 | } |
80 | | |
81 | | /* return 1 if a pid is a current child otherwise 0 */ |
82 | | int mworker_current_child(int pid) |
83 | 0 | { |
84 | 0 | struct mworker_proc *child; |
85 | |
|
86 | 0 | list_for_each_entry(child, &proc_list, list) { |
87 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (!(child->options & PROC_O_LEAVING)) && (child->pid == pid)) |
88 | 0 | return 1; |
89 | 0 | } |
90 | 0 | return 0; |
91 | 0 | } |
92 | | |
93 | | /* |
94 | | * Return the number of new and old children (including workers and external |
95 | | * processes) |
96 | | */ |
97 | | int mworker_child_nb() |
98 | 0 | { |
99 | 0 | struct mworker_proc *child; |
100 | 0 | int ret = 0; |
101 | |
|
102 | 0 | list_for_each_entry(child, &proc_list, list) { |
103 | 0 | if (child->options & PROC_O_TYPE_WORKER) |
104 | 0 | ret++; |
105 | 0 | } |
106 | |
|
107 | 0 | return ret; |
108 | 0 | } |
109 | | |
110 | | |
111 | | /* |
112 | | * serialize the proc list and put it in the environment |
113 | | */ |
114 | | void mworker_proc_list_to_env() |
115 | 0 | { |
116 | 0 | char *msg = NULL; |
117 | 0 | struct mworker_proc *child; |
118 | |
|
119 | 0 | list_for_each_entry(child, &proc_list, list) { |
120 | 0 | char type = '?'; |
121 | |
|
122 | 0 | if (child->options & PROC_O_TYPE_MASTER) |
123 | 0 | type = 'm'; |
124 | 0 | else if (child->options &= PROC_O_TYPE_WORKER) |
125 | 0 | type = 'w'; |
126 | |
|
127 | 0 | if (child->pid > -1) |
128 | 0 | memprintf(&msg, "%s|type=%c;fd=%d;cfd=%d;pid=%d;reloads=%d;failedreloads=%d;timestamp=%d;id=%s;version=%s", msg ? msg : "", type, child->ipc_fd[0], child->ipc_fd[1], child->pid, child->reloads, child->failedreloads, child->timestamp, child->id ? child->id : "", child->version); |
129 | 0 | } |
130 | 0 | if (msg) |
131 | 0 | setenv("HAPROXY_PROCESSES", msg, 1); |
132 | 0 | } |
133 | | |
134 | | struct mworker_proc *mworker_proc_new() |
135 | 0 | { |
136 | 0 | struct mworker_proc *child; |
137 | |
|
138 | 0 | child = calloc(1, sizeof(*child)); |
139 | 0 | if (!child) |
140 | 0 | return NULL; |
141 | | |
142 | 0 | child->failedreloads = 0; |
143 | 0 | child->reloads = 0; |
144 | 0 | child->pid = -1; |
145 | 0 | child->ipc_fd[0] = -1; |
146 | 0 | child->ipc_fd[1] = -1; |
147 | 0 | child->timestamp = -1; |
148 | |
|
149 | 0 | return child; |
150 | 0 | } |
151 | | |
152 | | |
153 | | /* |
154 | | * unserialize the proc list from the environment |
155 | | * Return < 0 upon error. |
156 | | */ |
157 | | int mworker_env_to_proc_list() |
158 | 0 | { |
159 | 0 | char *env, *msg, *omsg = NULL, *token = NULL, *s1; |
160 | 0 | struct mworker_proc *child; |
161 | 0 | int err = 0; |
162 | |
|
163 | 0 | env = getenv("HAPROXY_PROCESSES"); |
164 | 0 | if (!env) |
165 | 0 | goto no_env; |
166 | | |
167 | 0 | omsg = msg = strdup(env); |
168 | 0 | if (!msg) { |
169 | 0 | ha_alert("Out of memory while trying to allocate a worker process structure."); |
170 | 0 | err = -1; |
171 | 0 | goto out; |
172 | 0 | } |
173 | | |
174 | 0 | while ((token = strtok_r(msg, "|", &s1))) { |
175 | 0 | char *subtoken = NULL; |
176 | 0 | char *s2 = NULL; |
177 | |
|
178 | 0 | msg = NULL; |
179 | |
|
180 | 0 | child = mworker_proc_new(); |
181 | 0 | if (!child) { |
182 | 0 | ha_alert("out of memory while trying to allocate a worker process structure."); |
183 | 0 | err = -1; |
184 | 0 | goto out; |
185 | 0 | } |
186 | | |
187 | 0 | while ((subtoken = strtok_r(token, ";", &s2))) { |
188 | |
|
189 | 0 | token = NULL; |
190 | |
|
191 | 0 | if (strncmp(subtoken, "type=", 5) == 0) { |
192 | 0 | char type; |
193 | |
|
194 | 0 | type = *(subtoken+5); |
195 | 0 | if (type == 'm') { /* we are in the master, assign it */ |
196 | 0 | proc_self = child; |
197 | 0 | child->options |= PROC_O_TYPE_MASTER; |
198 | 0 | } else if (type == 'w') { |
199 | 0 | child->options |= PROC_O_TYPE_WORKER; |
200 | 0 | } |
201 | |
|
202 | 0 | } else if (strncmp(subtoken, "fd=", 3) == 0) { |
203 | 0 | child->ipc_fd[0] = atoi(subtoken+3); |
204 | 0 | if (child->ipc_fd[0] > -1) |
205 | 0 | global.maxsock++; |
206 | 0 | } else if (strncmp(subtoken, "cfd=", 4) == 0) { |
207 | 0 | child->ipc_fd[1] = atoi(subtoken+4); |
208 | 0 | if (child->ipc_fd[1] > -1) |
209 | 0 | global.maxsock++; |
210 | 0 | } else if (strncmp(subtoken, "pid=", 4) == 0) { |
211 | 0 | child->pid = atoi(subtoken+4); |
212 | 0 | } else if (strncmp(subtoken, "reloads=", 8) == 0) { |
213 | | /* we only increment the number of asked reload */ |
214 | 0 | child->reloads = atoi(subtoken+8); |
215 | 0 | } else if (strncmp(subtoken, "failedreloads=", 14) == 0) { |
216 | 0 | child->failedreloads = atoi(subtoken+14); |
217 | 0 | } else if (strncmp(subtoken, "timestamp=", 10) == 0) { |
218 | 0 | child->timestamp = atoi(subtoken+10); |
219 | 0 | } else if (strncmp(subtoken, "id=", 3) == 0) { |
220 | 0 | child->id = strdup(subtoken+3); |
221 | 0 | } else if (strncmp(subtoken, "version=", 8) == 0) { |
222 | 0 | child->version = strdup(subtoken+8); |
223 | 0 | } |
224 | 0 | } |
225 | 0 | if (child->pid > 0) { |
226 | 0 | LIST_APPEND(&proc_list, &child->list); |
227 | 0 | } else { |
228 | 0 | mworker_free_child(child); |
229 | 0 | } |
230 | 0 | } |
231 | | |
232 | | /* set the leaving processes once we know which number of reloads are the current processes */ |
233 | | |
234 | 0 | list_for_each_entry(child, &proc_list, list) { |
235 | 0 | if (child->reloads > 0) |
236 | 0 | child->options |= PROC_O_LEAVING; |
237 | 0 | } |
238 | |
|
239 | 0 | unsetenv("HAPROXY_PROCESSES"); |
240 | |
|
241 | 0 | no_env: |
242 | |
|
243 | 0 | if (!proc_self) { |
244 | |
|
245 | 0 | proc_self = mworker_proc_new(); |
246 | 0 | if (!proc_self) { |
247 | 0 | ha_alert("Cannot allocate process structures.\n"); |
248 | 0 | err = -1; |
249 | 0 | goto out; |
250 | 0 | } |
251 | 0 | proc_self->options |= PROC_O_TYPE_MASTER; |
252 | 0 | proc_self->pid = pid; |
253 | 0 | proc_self->timestamp = 0; /* we don't know the startime anymore */ |
254 | |
|
255 | 0 | LIST_APPEND(&proc_list, &proc_self->list); |
256 | 0 | ha_warning("The master internals are corrupted or it was started with a too old version (< 1.9). Please restart the master process.\n"); |
257 | 0 | } |
258 | | |
259 | 0 | out: |
260 | 0 | free(omsg); |
261 | 0 | return err; |
262 | 0 | } |
263 | | |
264 | | /* Signal blocking and unblocking */ |
265 | | |
266 | | void mworker_block_signals() |
267 | 0 | { |
268 | 0 | sigset_t set; |
269 | |
|
270 | 0 | sigemptyset(&set); |
271 | 0 | sigaddset(&set, SIGUSR1); |
272 | 0 | sigaddset(&set, SIGUSR2); |
273 | 0 | sigaddset(&set, SIGTTIN); |
274 | 0 | sigaddset(&set, SIGTTOU); |
275 | 0 | sigaddset(&set, SIGHUP); |
276 | 0 | sigaddset(&set, SIGCHLD); |
277 | 0 | ha_sigmask(SIG_SETMASK, &set, NULL); |
278 | 0 | } |
279 | | |
280 | | void mworker_unblock_sigchld() |
281 | 0 | { |
282 | 0 | sigset_t set; |
283 | |
|
284 | 0 | signal_register_fct(SIGCHLD, mworker_catch_sigchld, SIGCHLD); |
285 | |
|
286 | 0 | sigemptyset(&set); |
287 | 0 | sigaddset(&set, SIGCHLD); |
288 | |
|
289 | 0 | ha_sigmask(SIG_UNBLOCK, &set, NULL); |
290 | 0 | } |
291 | | |
292 | | void mworker_unblock_signals() |
293 | 0 | { |
294 | 0 | signal_unregister(SIGTTIN); |
295 | 0 | signal_unregister(SIGTTOU); |
296 | 0 | signal_unregister(SIGUSR1); |
297 | 0 | signal_unregister(SIGHUP); |
298 | 0 | signal_unregister(SIGQUIT); |
299 | |
|
300 | 0 | signal_register_fct(SIGTERM, mworker_catch_sigterm, SIGTERM); |
301 | 0 | signal_register_fct(SIGUSR1, mworker_catch_sigterm, SIGUSR1); |
302 | 0 | signal_register_fct(SIGTTIN, mworker_broadcast_signal, SIGTTIN); |
303 | 0 | signal_register_fct(SIGTTOU, mworker_broadcast_signal, SIGTTOU); |
304 | 0 | signal_register_fct(SIGINT, mworker_catch_sigterm, SIGINT); |
305 | 0 | signal_register_fct(SIGHUP, mworker_catch_sighup, SIGHUP); |
306 | 0 | signal_register_fct(SIGUSR2, mworker_catch_sighup, SIGUSR2); |
307 | 0 | signal_register_fct(SIGCHLD, mworker_catch_sigchld, SIGCHLD); |
308 | |
|
309 | 0 | haproxy_unblock_signals(); |
310 | 0 | } |
311 | | |
312 | | /* ----- mworker signal handlers ----- */ |
313 | | |
314 | | /* broadcast the configured signal to the workers */ |
315 | | void mworker_broadcast_signal(struct sig_handler *sh) |
316 | 0 | { |
317 | 0 | mworker_kill(sh->arg); |
318 | 0 | } |
319 | | |
320 | | /* |
321 | | * When called, this function reexec haproxy with -sf followed by current |
322 | | * children PIDs and possibly old children PIDs if they didn't leave yet. |
323 | | */ |
324 | | static void mworker_reexec(int hardreload) |
325 | 0 | { |
326 | 0 | char **next_argv = NULL; |
327 | 0 | int old_argc = 0; /* previous number of argument */ |
328 | 0 | int next_argc = 0; |
329 | 0 | int i = 0; |
330 | 0 | char *msg = NULL; |
331 | 0 | struct rlimit limit; |
332 | 0 | struct mworker_proc *current_child = NULL; |
333 | 0 | int x_off = 0; /* disable -x by putting -x /dev/null */ |
334 | |
|
335 | 0 | mworker_block_signals(); |
336 | | |
337 | | /* restore initial environment (before parsing the config) and do re-exec. |
338 | | * The initial process environment should be restored here, preceded by |
339 | | * clean_env(), which do the same job as clearenv(). |
340 | | * Otherwise, after the re-exec we will start the new worker in the |
341 | | * environment modified by '*env' keywords from the previous configuration, |
342 | | * i.e. existed before the reload. |
343 | | */ |
344 | 0 | if (clean_env() != 0) { |
345 | 0 | ha_alert("Master encountered a non-recoverable error, exiting.\n"); |
346 | 0 | exit(EXIT_FAILURE); |
347 | 0 | } |
348 | | |
349 | 0 | if (restore_env() != 0) { |
350 | 0 | ha_alert("Master encountered a non-recoverable error, exiting.\n"); |
351 | 0 | exit(EXIT_FAILURE); |
352 | 0 | } |
353 | | |
354 | 0 | setenv("HAPROXY_MWORKER_REEXEC", "1", 1); |
355 | |
|
356 | 0 | mworker_proc_list_to_env(); /* put the children description in the env */ |
357 | | |
358 | | /* during the reload we must ensure that every FDs that can't be |
359 | | * reuse (ie those that are not referenced in the proc_list) |
360 | | * are closed or they will leak. */ |
361 | | |
362 | | /* close the listeners FD */ |
363 | 0 | mworker_cli_proxy_stop(); |
364 | |
|
365 | 0 | if (fdtab) |
366 | 0 | deinit_pollers(); |
367 | |
|
368 | | #ifdef HAVE_SSL_RAND_KEEP_RANDOM_DEVICES_OPEN |
369 | | /* close random device FDs */ |
370 | | RAND_keep_random_devices_open(0); |
371 | | #endif |
372 | | |
373 | | /* restore the initial FD limits */ |
374 | 0 | limit.rlim_cur = rlim_fd_cur_at_boot; |
375 | 0 | limit.rlim_max = rlim_fd_max_at_boot; |
376 | 0 | if (raise_rlim_nofile(&limit, &limit) != 0) { |
377 | 0 | ha_warning("Failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n", |
378 | 0 | rlim_fd_cur_at_boot, rlim_fd_max_at_boot, |
379 | 0 | (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max); |
380 | 0 | } |
381 | | |
382 | | /* compute length */ |
383 | 0 | while (old_argv[old_argc]) |
384 | 0 | old_argc++; |
385 | | |
386 | | /* 1 for haproxy -sf, 2 for -x /socket */ |
387 | 0 | next_argv = calloc(old_argc + 1 + 2 + mworker_child_nb() + 1, |
388 | 0 | sizeof(*next_argv)); |
389 | 0 | if (next_argv == NULL) |
390 | 0 | goto alloc_error; |
391 | | |
392 | | /* copy the program name */ |
393 | 0 | next_argv[next_argc++] = old_argv[0]; |
394 | | |
395 | | /* we need to reintroduce /dev/null every time */ |
396 | 0 | if (old_unixsocket && strcmp(old_unixsocket, "/dev/null") == 0) |
397 | 0 | x_off = 1; |
398 | | |
399 | | /* insert the new options just after argv[0] in case we have a -- */ |
400 | | |
401 | | /* add -sf <PID>* to argv */ |
402 | 0 | if (mworker_child_nb() > 0) { |
403 | 0 | struct mworker_proc *child; |
404 | |
|
405 | 0 | if (hardreload) |
406 | 0 | next_argv[next_argc++] = "-st"; |
407 | 0 | else |
408 | 0 | next_argv[next_argc++] = "-sf"; |
409 | |
|
410 | 0 | list_for_each_entry(child, &proc_list, list) { |
411 | 0 | if (!(child->options & PROC_O_LEAVING) && (child->options & PROC_O_TYPE_WORKER)) |
412 | 0 | current_child = child; |
413 | |
|
414 | 0 | if (!(child->options & (PROC_O_TYPE_WORKER)) || child->pid <= -1) |
415 | 0 | continue; |
416 | 0 | if ((next_argv[next_argc++] = memprintf(&msg, "%d", child->pid)) == NULL) |
417 | 0 | goto alloc_error; |
418 | 0 | msg = NULL; |
419 | 0 | } |
420 | 0 | } |
421 | 0 | if (!x_off && current_child) { |
422 | | /* add the -x option with the socketpair of the current worker */ |
423 | 0 | next_argv[next_argc++] = "-x"; |
424 | 0 | if ((next_argv[next_argc++] = memprintf(&msg, "sockpair@%d", current_child->ipc_fd[0])) == NULL) |
425 | 0 | goto alloc_error; |
426 | 0 | msg = NULL; |
427 | 0 | } |
428 | | |
429 | 0 | if (x_off) { |
430 | | /* if the cmdline contained a -x /dev/null, continue to use it */ |
431 | 0 | next_argv[next_argc++] = "-x"; |
432 | 0 | next_argv[next_argc++] = "/dev/null"; |
433 | 0 | } |
434 | | |
435 | | /* copy the previous options */ |
436 | 0 | for (i = 1; i < old_argc; i++) |
437 | 0 | next_argv[next_argc++] = old_argv[i]; |
438 | | |
439 | | /* need to withdraw MODE_STARTING from master, because we have to free |
440 | | * the startup logs ring here, see more details in print_message() |
441 | | */ |
442 | 0 | global.mode &= ~MODE_STARTING; |
443 | 0 | startup_logs_free(startup_logs); |
444 | |
|
445 | 0 | signal(SIGPROF, SIG_IGN); |
446 | 0 | execvp(next_argv[0], next_argv); |
447 | 0 | ha_warning("Failed to reexecute the master process [%d]: %s\n", pid, strerror(errno)); |
448 | 0 | ha_free(&next_argv); |
449 | 0 | return; |
450 | | |
451 | 0 | alloc_error: |
452 | 0 | ha_free(&next_argv); |
453 | 0 | ha_warning("Failed to reexecute the master process [%d]: Cannot allocate memory\n", pid); |
454 | 0 | return; |
455 | 0 | } |
456 | | |
457 | | /* reload haproxy and emit a warning */ |
458 | | static void mworker_reload(int hardreload) |
459 | 0 | { |
460 | 0 | struct mworker_proc *child; |
461 | 0 | struct per_thread_deinit_fct *ptdf; |
462 | |
|
463 | 0 | ha_notice("Reloading HAProxy%s\n", hardreload?" (hard-reload)":""); |
464 | | |
465 | | /* close the poller FD and the thread waker pipe FD */ |
466 | 0 | list_for_each_entry(ptdf, &per_thread_deinit_list, list) |
467 | 0 | ptdf->fct(); |
468 | | |
469 | | /* increment the number of reloads, child->reloads is checked in |
470 | | * mworker_env_to_proc_list() (after reload) in order to set |
471 | | * PROC_O_LEAVING flag for the process |
472 | | */ |
473 | 0 | list_for_each_entry(child, &proc_list, list) { |
474 | 0 | child->reloads++; |
475 | 0 | } |
476 | |
|
477 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) { |
478 | 0 | struct timespec ts; |
479 | |
|
480 | 0 | (void)clock_gettime(CLOCK_MONOTONIC, &ts); |
481 | |
|
482 | 0 | sd_notifyf(0, |
483 | 0 | "RELOADING=1\n" |
484 | 0 | "STATUS=Reloading Configuration.\n" |
485 | 0 | "MONOTONIC_USEC=%" PRIu64 "\n", |
486 | 0 | (ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL)); |
487 | 0 | } |
488 | 0 | mworker_reexec(hardreload); |
489 | 0 | } |
490 | | |
491 | | /* |
492 | | * When called, this function reexec haproxy with -sf followed by current |
493 | | * children PIDs and possibly old children PIDs if they didn't leave yet. |
494 | | */ |
495 | | void mworker_catch_sighup(struct sig_handler *sh) |
496 | 0 | { |
497 | 0 | mworker_reload(0); |
498 | 0 | } |
499 | | |
500 | | void mworker_catch_sigterm(struct sig_handler *sh) |
501 | 0 | { |
502 | 0 | int sig = sh->arg; |
503 | |
|
504 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) { |
505 | 0 | sd_notify(0, "STOPPING=1"); |
506 | 0 | } |
507 | 0 | ha_warning("Exiting Master process...\n"); |
508 | 0 | mworker_kill(sig); |
509 | 0 | } |
510 | | |
511 | | /* handle operations that can't be done in the signal handler */ |
512 | | static struct task *mworker_task_child_failure(struct task *task, void *context, unsigned int state) |
513 | 0 | { |
514 | 0 | mworker_unblock_signals(); |
515 | 0 | task_destroy(task); |
516 | 0 | return NULL; |
517 | 0 | } |
518 | | |
519 | | /* |
520 | | * Performs some routines for the worker process, which has failed the reload, |
521 | | * updates the global load_status. |
522 | | */ |
523 | | static void mworker_on_new_child_failure(int exitpid, int status) |
524 | 0 | { |
525 | 0 | struct mworker_proc *child; |
526 | 0 | struct task *t; |
527 | | |
528 | | /* increment the number of failed reloads */ |
529 | 0 | list_for_each_entry(child, &proc_list, list) { |
530 | 0 | child->failedreloads++; |
531 | 0 | } |
532 | | |
533 | | /* do not keep unused FDs retrieved from the previous process */ |
534 | 0 | sock_drop_unused_old_sockets(); |
535 | |
|
536 | 0 | usermsgs_clr(NULL); |
537 | 0 | load_status = 0; |
538 | 0 | ha_warning("Failed to load worker (%d) exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128): "Exit"); |
539 | | /* the sd_notify API is not able to send a reload failure signal. So |
540 | | * the READY=1 signal still need to be sent */ |
541 | 0 | if (global.tune.options & GTUNE_USE_SYSTEMD) |
542 | 0 | sd_notify(0, "READY=1\nSTATUS=Reload failed!\n"); |
543 | | |
544 | | /* call a task to unblock the signals from outside the sig handler */ |
545 | 0 | if ((t = task_new_here()) == NULL) { |
546 | 0 | ha_warning("Can't restore HAProxy signals!\n"); |
547 | 0 | return; |
548 | 0 | } |
549 | | |
550 | 0 | t->process = mworker_task_child_failure; |
551 | 0 | task_wakeup(t, TASK_WOKEN_MSG); |
552 | 0 | } |
553 | | |
554 | | /* |
555 | | * Wait for every children to exit |
556 | | */ |
557 | | |
558 | | void mworker_catch_sigchld(struct sig_handler *sh) |
559 | 0 | { |
560 | 0 | int exitpid = -1; |
561 | 0 | int status = 0; |
562 | 0 | int childfound; |
563 | 0 | struct listener *l, *l_next; |
564 | 0 | struct proxy *curproxy; |
565 | |
|
566 | 0 | restart_wait: |
567 | |
|
568 | 0 | childfound = 0; |
569 | |
|
570 | 0 | exitpid = waitpid(-1, &status, WNOHANG); |
571 | 0 | if (exitpid > 0) { |
572 | 0 | struct mworker_proc *child, *it; |
573 | |
|
574 | 0 | if (WIFEXITED(status)) |
575 | 0 | status = WEXITSTATUS(status); |
576 | 0 | else if (WIFSIGNALED(status)) |
577 | 0 | status = 128 + WTERMSIG(status); |
578 | 0 | else if (WIFSTOPPED(status)) |
579 | 0 | status = 128 + WSTOPSIG(status); |
580 | 0 | else |
581 | 0 | status = 255; |
582 | | |
583 | | /* delete the child from the process list */ |
584 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
585 | 0 | if (child->pid != exitpid) |
586 | 0 | continue; |
587 | | |
588 | 0 | LIST_DELETE(&child->list); |
589 | 0 | childfound = 1; |
590 | 0 | break; |
591 | 0 | } |
592 | |
|
593 | 0 | if (!childfound) { |
594 | | /* We didn't find the PID in the list, that shouldn't happen but we can emit a warning */ |
595 | 0 | ha_warning("Process %d exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit"); |
596 | 0 | } else if (child->options & PROC_O_INIT) { |
597 | 0 | mworker_on_new_child_failure(exitpid, status); |
598 | | |
599 | | /* Detach all listeners */ |
600 | 0 | for (curproxy = proxies_list; curproxy; curproxy = curproxy->next) { |
601 | 0 | list_for_each_entry_safe(l, l_next, &curproxy->conf.listeners, by_fe) { |
602 | 0 | if ((l->rx.fd == child->ipc_fd[0]) || (l->rx.fd == child->ipc_fd[1])) { |
603 | 0 | unbind_listener(l); |
604 | 0 | delete_listener(l); |
605 | 0 | } |
606 | 0 | } |
607 | 0 | } |
608 | | |
609 | | /* Drop server */ |
610 | 0 | if (child->srv) { |
611 | 0 | srv_detach(child->srv); |
612 | 0 | srv_drop(child->srv); |
613 | 0 | } |
614 | | |
615 | | /* Delete fd from poller fdtab, which will close it */ |
616 | 0 | fd_delete(child->ipc_fd[0]); |
617 | 0 | child->ipc_fd[0] = -1; |
618 | 0 | mworker_free_child(child); |
619 | 0 | child = NULL; |
620 | | |
621 | | /* When worker fails during the first startup, there is |
622 | | * no previous workers with state PROC_O_LEAVING, master |
623 | | * process should exit here as well to keep the |
624 | | * previous behaviour |
625 | | */ |
626 | 0 | if ((proc_self->options & PROC_O_TYPE_MASTER) && (proc_self->reloads == 0)) |
627 | 0 | exit(status); |
628 | 0 | } else { |
629 | | /* check if exited child is a current child */ |
630 | 0 | if (!(child->options & PROC_O_LEAVING)) { |
631 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
632 | 0 | fd_delete(child->ipc_fd[0]); |
633 | 0 | if (status < 128) |
634 | 0 | ha_warning("Current worker (%d) exited with code %d (%s)\n", exitpid, status, "Exit"); |
635 | 0 | else |
636 | 0 | ha_alert("Current worker (%d) exited with code %d (%s)\n", exitpid, status, strsignal(status - 128)); |
637 | 0 | } |
638 | |
|
639 | 0 | if (status != 0 && status != 130 && status != 143) { |
640 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
641 | 0 | ha_warning("A worker process unexpectedly died and this can only be explained by a bug in haproxy or its dependencies.\nPlease check that you are running an up to date and maintained version of haproxy and open a bug report.\n"); |
642 | 0 | display_version(); |
643 | 0 | } |
644 | | /* new worker, which has been launched at reload has status PROC_O_INIT */ |
645 | 0 | if (!(global.tune.options & GTUNE_NOEXIT_ONFAILURE) && !(child->options & PROC_O_INIT)) { |
646 | 0 | ha_alert("exit-on-failure: killing every processes with SIGTERM\n"); |
647 | 0 | mworker_kill(SIGTERM); |
648 | 0 | } |
649 | 0 | } |
650 | | /* 0 & SIGTERM (143) are normal, but we should report SIGINT (130) and other signals */ |
651 | 0 | if (exitcode < 0 && status != 0 && status != 143) |
652 | 0 | exitcode = status; |
653 | 0 | } else { |
654 | 0 | if (child->options & PROC_O_TYPE_WORKER) { |
655 | 0 | if (child->reloads > max_reloads) |
656 | 0 | ha_warning("Former worker (%d) exited with code %d (%s), as it exceeds max reloads (%d)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit", max_reloads); |
657 | 0 | else |
658 | 0 | ha_warning("Former worker (%d) exited with code %d (%s)\n", exitpid, status, (status >= 128) ? strsignal(status - 128) : "Exit"); |
659 | | /* Delete fd from poller fdtab, which will close it */ |
660 | 0 | fd_delete(child->ipc_fd[0]); |
661 | 0 | delete_oldpid(exitpid); |
662 | 0 | } |
663 | 0 | } |
664 | 0 | mworker_free_child(child); |
665 | 0 | child = NULL; |
666 | 0 | } |
667 | | |
668 | | /* do it again to check if it was the last worker */ |
669 | 0 | goto restart_wait; |
670 | 0 | } |
671 | | /* Better rely on the system than on a list of process to check if it was the last one */ |
672 | 0 | else if (exitpid == -1 && errno == ECHILD) { |
673 | 0 | struct post_deinit_fct *pdff; |
674 | |
|
675 | 0 | ha_warning("All workers exited. Exiting... (%d)\n", (exitcode > 0) ? exitcode : EXIT_SUCCESS); |
676 | |
|
677 | 0 | list_for_each_entry(pdff, &post_deinit_master_list, list) |
678 | 0 | pdff->fct(); |
679 | |
|
680 | 0 | atexit_flag = 0; |
681 | 0 | if (exitcode > 0) |
682 | 0 | exit(exitcode); /* parent must leave using the status code that provoked the exit */ |
683 | 0 | exit(EXIT_SUCCESS); |
684 | 0 | } |
685 | |
|
686 | 0 | } |
687 | | |
688 | | /* ----- IPC FD (sockpair) related ----- */ |
689 | | |
690 | | /* This wrapper is called from the workers. It is registered instead of the |
691 | | * normal listener_accept() so the worker can exit() when it detects that the |
692 | | * master closed the IPC FD. If it's not a close, we just call the regular |
693 | | * listener_accept() function. |
694 | | */ |
695 | | void mworker_accept_wrapper(int fd) |
696 | 0 | { |
697 | 0 | char c; |
698 | 0 | int ret; |
699 | |
|
700 | 0 | while (1) { |
701 | 0 | ret = recv(fd, &c, 1, MSG_PEEK); |
702 | 0 | if (ret == -1) { |
703 | 0 | if (errno == EINTR) |
704 | 0 | continue; |
705 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
706 | 0 | fd_cant_recv(fd); |
707 | 0 | return; |
708 | 0 | } |
709 | 0 | break; |
710 | 0 | } else if (ret > 0) { |
711 | 0 | struct listener *l = fdtab[fd].owner; |
712 | |
|
713 | 0 | if (l) |
714 | 0 | listener_accept(l); |
715 | 0 | return; |
716 | 0 | } else if (ret == 0) { |
717 | | /* At this step the master is down before |
718 | | * this worker perform a 'normal' exit. |
719 | | * So we want to exit with an error but |
720 | | * other threads could currently process |
721 | | * some stuff so we can't perform a clean |
722 | | * deinit(). |
723 | | */ |
724 | 0 | exit(EXIT_FAILURE); |
725 | 0 | } |
726 | 0 | } |
727 | 0 | return; |
728 | 0 | } |
729 | | |
730 | | /* |
731 | | * This function registers the accept wrapper for the sockpair of the master |
732 | | * worker. It's only handled by worker thread #0. Other threads and master do |
733 | | * nothing here. It always returns 1 (success). |
734 | | */ |
735 | | static int mworker_sockpair_register_per_thread() |
736 | 0 | { |
737 | 0 | if (!(global.mode & MODE_MWORKER) || master) |
738 | 0 | return 1; |
739 | | |
740 | 0 | if (tid != 0) |
741 | 0 | return 1; |
742 | | |
743 | 0 | if (proc_self->ipc_fd[1] < 0) /* proc_self was incomplete and we can't find the socketpair */ |
744 | 0 | return 1; |
745 | | |
746 | 0 | fd_set_nonblock(proc_self->ipc_fd[1]); |
747 | | /* register the wrapper to handle read 0 when the master exits */ |
748 | 0 | fdtab[proc_self->ipc_fd[1]].iocb = mworker_accept_wrapper; |
749 | 0 | fd_want_recv(proc_self->ipc_fd[1]); |
750 | 0 | return 1; |
751 | 0 | } |
752 | | |
753 | | REGISTER_PER_THREAD_INIT(mworker_sockpair_register_per_thread); |
754 | | |
755 | | /* ----- proxies ----- */ |
756 | | /* |
757 | | * Upon a reload, the master worker needs to close all listeners FDs but the mworker_pipe |
758 | | * fd, and the FD provided by fd@ |
759 | | */ |
760 | | void mworker_cleanlisteners() |
761 | 0 | { |
762 | 0 | struct listener *l, *l_next; |
763 | 0 | struct proxy *curproxy; |
764 | 0 | struct peers *curpeers; |
765 | | |
766 | | /* peers proxies cleanup */ |
767 | 0 | for (curpeers = cfg_peers; curpeers; curpeers = curpeers->next) { |
768 | 0 | if (!curpeers->peers_fe) |
769 | 0 | continue; |
770 | | |
771 | 0 | stop_proxy(curpeers->peers_fe); |
772 | | /* disable this peer section so that it kills itself */ |
773 | 0 | if (curpeers->sighandler) |
774 | 0 | signal_unregister_handler(curpeers->sighandler); |
775 | 0 | task_destroy(curpeers->sync_task); |
776 | 0 | curpeers->sync_task = NULL; |
777 | 0 | curpeers->peers_fe = NULL; |
778 | 0 | } |
779 | | |
780 | | /* main proxies cleanup */ |
781 | 0 | for (curproxy = proxies_list; curproxy; curproxy = curproxy->next) { |
782 | 0 | int listen_in_master = 0; |
783 | |
|
784 | 0 | list_for_each_entry_safe(l, l_next, &curproxy->conf.listeners, by_fe) { |
785 | | /* remove the listener, but not those we need in the master... */ |
786 | 0 | if (!(l->rx.flags & RX_F_MWORKER)) { |
787 | 0 | unbind_listener(l); |
788 | 0 | delete_listener(l); |
789 | 0 | } else { |
790 | 0 | listen_in_master = 1; |
791 | 0 | } |
792 | 0 | } |
793 | | /* if the proxy shouldn't be in the master, we stop it */ |
794 | 0 | if (!listen_in_master) |
795 | 0 | curproxy->flags |= PR_FL_DISABLED; |
796 | 0 | } |
797 | 0 | } |
798 | | |
799 | | /* Upon a configuration loading error some mworker_proc and FDs/server were |
800 | | * assigned but the worker was never forked, we must close the FDs and |
801 | | * remove the server |
802 | | */ |
803 | | void mworker_cleanup_proc() |
804 | 0 | { |
805 | 0 | struct mworker_proc *child, *it; |
806 | |
|
807 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
808 | |
|
809 | 0 | if (child->pid == -1) { |
810 | | /* Close the socketpairs. */ |
811 | 0 | if (child->ipc_fd[0] > -1) |
812 | 0 | close(child->ipc_fd[0]); |
813 | 0 | if (child->ipc_fd[1] > -1) |
814 | 0 | close(child->ipc_fd[1]); |
815 | 0 | if (child->srv) { |
816 | | /* only exists if we created a master CLI listener */ |
817 | 0 | srv_detach(child->srv); |
818 | 0 | srv_drop(child->srv); |
819 | 0 | } |
820 | 0 | LIST_DELETE(&child->list); |
821 | 0 | mworker_free_child(child); |
822 | 0 | } |
823 | 0 | } |
824 | 0 | } |
825 | | |
826 | | struct cli_showproc_ctx { |
827 | | int debug; |
828 | | int next_reload; /* reload number to resume from, 0 = from the beginning */ |
829 | | }; |
830 | | |
831 | | /* Append a single worker row to trash (shared between current/old sections) */ |
832 | | static void cli_append_worker_row(struct cli_showproc_ctx *ctx, struct mworker_proc *child, time_t tv_sec) |
833 | 0 | { |
834 | 0 | char *uptime = NULL; |
835 | 0 | int up = tv_sec - child->timestamp; |
836 | |
|
837 | 0 | if (up < 0) /* must never be negative because of clock drift */ |
838 | 0 | up = 0; |
839 | |
|
840 | 0 | memprintf(&uptime, "%dd%02dh%02dm%02ds", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60)); |
841 | 0 | chunk_appendf(&trash, "%-15u %-15s %-15d %-15s %-15s", child->pid, "worker", child->reloads, uptime, child->version); |
842 | 0 | if (ctx->debug) |
843 | 0 | chunk_appendf(&trash, "\t\t %-15d %-15d", child->ipc_fd[0], child->ipc_fd[1]); |
844 | 0 | chunk_appendf(&trash, "\n"); |
845 | 0 | ha_free(&uptime); |
846 | 0 | } |
847 | | |
848 | | /* Displays workers and processes */ |
849 | | static int cli_io_handler_show_proc(struct appctx *appctx) |
850 | 0 | { |
851 | 0 | struct mworker_proc *child; |
852 | 0 | int old = 0; |
853 | 0 | int up = date.tv_sec - proc_self->timestamp; |
854 | 0 | struct cli_showproc_ctx *ctx = appctx->svcctx; |
855 | 0 | char *uptime = NULL; |
856 | 0 | char *reloadtxt = NULL; |
857 | |
|
858 | 0 | if (up < 0) /* must never be negative because of clock drift */ |
859 | 0 | up = 0; |
860 | |
|
861 | 0 | chunk_reset(&trash); |
862 | |
|
863 | 0 | if (ctx->next_reload == 0) { |
864 | 0 | memprintf(&reloadtxt, "%d [failed: %d]", proc_self->reloads, proc_self->failedreloads); |
865 | 0 | chunk_printf(&trash, "#%-14s %-15s %-15s %-15s %-15s", "<PID>", "<type>", "<reloads>", "<uptime>", "<version>"); |
866 | 0 | if (ctx->debug) |
867 | 0 | chunk_appendf(&trash, "\t\t %-15s %-15s", "<ipc_fd[0]>", "<ipc_fd[1]>"); |
868 | 0 | chunk_appendf(&trash, "\n"); |
869 | | |
870 | | /* display the master only the first time */ |
871 | 0 | memprintf(&uptime, "%dd%02dh%02dm%02ds", up / 86400, (up % 86400) / 3600, (up % 3600) / 60, (up % 60)); |
872 | 0 | chunk_appendf(&trash, "%-15u %-15s %-15s %-15s %-15s", (unsigned int)getpid(), "master", reloadtxt, uptime, haproxy_version); |
873 | 0 | if (ctx->debug) |
874 | 0 | chunk_appendf(&trash, "\t\t %-15d %-15d", proc_self->ipc_fd[0], proc_self->ipc_fd[1]); |
875 | 0 | chunk_appendf(&trash, "\n"); |
876 | 0 | } |
877 | 0 | ha_free(&reloadtxt); |
878 | 0 | ha_free(&uptime); |
879 | | |
880 | | /* displays current processes */ |
881 | 0 | if (ctx->next_reload == 0) |
882 | 0 | chunk_appendf(&trash, "# workers\n"); |
883 | 0 | list_for_each_entry(child, &proc_list, list) { |
884 | | |
885 | | /* don't display current worker if we only need the next ones */ |
886 | 0 | if (ctx->next_reload != 0) |
887 | 0 | continue; |
888 | | |
889 | 0 | if (!(child->options & PROC_O_TYPE_WORKER)) |
890 | 0 | continue; |
891 | | |
892 | 0 | if (child->options & PROC_O_LEAVING) { |
893 | 0 | old++; |
894 | 0 | continue; |
895 | 0 | } |
896 | 0 | cli_append_worker_row(ctx, child, date.tv_sec); |
897 | 0 | } |
898 | |
|
899 | 0 | if (applet_putchk(appctx, &trash) == -1) |
900 | 0 | return 0; |
901 | | |
902 | | /* displays old processes */ |
903 | 0 | if (old || ctx->next_reload) { /* there's more */ |
904 | 0 | if (ctx->next_reload == 0) |
905 | 0 | chunk_appendf(&trash, "# old workers\n"); |
906 | 0 | list_for_each_entry(child, &proc_list, list) { |
907 | | /* If we're resuming, skip entries that were already printed (reload >= ctx->next_reload) */ |
908 | 0 | if (ctx->next_reload && child->reloads >= ctx->next_reload) |
909 | 0 | continue; |
910 | | |
911 | 0 | if (!(child->options & PROC_O_TYPE_WORKER)) |
912 | 0 | continue; |
913 | | |
914 | 0 | if (child->options & PROC_O_LEAVING) { |
915 | 0 | cli_append_worker_row(ctx, child, date.tv_sec); |
916 | | |
917 | | /* Try to flush so we can resume after this reload on next page if the buffer is full. */ |
918 | 0 | if (applet_putchk(appctx, &trash) == -1) { |
919 | | /* resume at this reload (exclude it on next pass) */ |
920 | 0 | ctx->next_reload = child->reloads; /* resume after entries >= this reload */ |
921 | 0 | return 0; |
922 | 0 | } |
923 | 0 | chunk_reset(&trash); |
924 | 0 | } |
925 | |
|
926 | 0 | } |
927 | 0 | } |
928 | | |
929 | | /* dump complete: reset resume cursor so next 'show proc' starts from the top */ |
930 | 0 | ctx->next_reload = 0; |
931 | 0 | return 1; |
932 | 0 | } |
933 | | |
934 | | /* reload the master process */ |
935 | | static int cli_parse_show_proc(char **args, char *payload, struct appctx *appctx, void *private) |
936 | 0 | { |
937 | 0 | struct cli_showproc_ctx *ctx; |
938 | |
|
939 | 0 | ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
940 | |
|
941 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
942 | 0 | return 1; |
943 | | |
944 | 0 | if (*args[2]) { |
945 | |
|
946 | 0 | if (strcmp(args[2], "debug") == 0) |
947 | 0 | ctx->debug = 1; |
948 | 0 | else |
949 | 0 | return cli_err(appctx, "'show proc' only supports 'debug' as argument\n"); |
950 | 0 | } |
951 | | |
952 | 0 | return 0; |
953 | 0 | } |
954 | | |
955 | | /* reload the master process */ |
956 | | static int cli_parse_reload(char **args, char *payload, struct appctx *appctx, void *private) |
957 | 0 | { |
958 | 0 | struct stconn *scb = NULL; |
959 | 0 | struct stream *strm = NULL; |
960 | 0 | struct connection *conn = NULL; |
961 | 0 | int fd = -1; |
962 | 0 | int hardreload = 0; |
963 | 0 | struct mworker_proc *proc; |
964 | |
|
965 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
966 | 0 | return 1; |
967 | | |
968 | 0 | list_for_each_entry(proc, &proc_list, list) { |
969 | | /* if there is a process with PROC_O_INIT, i.e. new worker is |
970 | | * doing its init routine, block the reload |
971 | | */ |
972 | 0 | if (proc->options & PROC_O_INIT) { |
973 | 0 | chunk_printf(&trash, "Success=0\n"); |
974 | 0 | chunk_appendf(&trash, "--\n"); |
975 | 0 | chunk_appendf(&trash, "Another reload is still in progress.\n"); |
976 | |
|
977 | 0 | if (applet_putchk(appctx, &trash) == -1) |
978 | 0 | return 0; |
979 | | |
980 | 0 | return 1; |
981 | 0 | } |
982 | 0 | } |
983 | | |
984 | | /* hard reload requested */ |
985 | 0 | if (*args[0] == 'h') |
986 | 0 | hardreload = 1; |
987 | | |
988 | | /* This ask for a synchronous reload, which means we will keep this FD |
989 | | instead of closing it. */ |
990 | |
|
991 | 0 | scb = appctx_sc(appctx); |
992 | 0 | if (scb) |
993 | 0 | strm = sc_strm(scb); |
994 | 0 | if (strm && strm->scf) |
995 | 0 | conn = sc_conn(strm->scf); |
996 | 0 | if (conn) |
997 | 0 | fd = conn_fd(conn); |
998 | | |
999 | | /* Send the FD of the current session to the "cli_reload" FD, which won't be polled */ |
1000 | 0 | if (fd != -1 && send_fd_uxst(proc_self->ipc_fd[0], fd) == 0) { |
1001 | 0 | fd_delete(fd); /* avoid the leak of the FD after sending it via the socketpair */ |
1002 | 0 | } |
1003 | 0 | mworker_reload(hardreload); |
1004 | |
|
1005 | 0 | return 1; |
1006 | 0 | } |
1007 | | |
1008 | | /* Displays if the current reload failed or succeed. |
1009 | | * If the startup-logs is available, dump it. */ |
1010 | | static int cli_io_handler_show_loadstatus(struct appctx *appctx) |
1011 | 0 | { |
1012 | 0 | struct mworker_proc *proc; |
1013 | |
|
1014 | 0 | if (!cli_has_level(appctx, ACCESS_LVL_OPER)) |
1015 | 0 | return 1; |
1016 | | |
1017 | | /* if the worker is still in the process of starting, we have to |
1018 | | * wait a little bit before trying again to get a final status. |
1019 | | */ |
1020 | 0 | list_for_each_entry(proc, &proc_list, list) { |
1021 | 0 | if (proc->options & PROC_O_INIT) { |
1022 | 0 | appctx->t->expire = tick_add(now_ms, 50); |
1023 | 0 | return 0; |
1024 | 0 | } |
1025 | 0 | } |
1026 | | |
1027 | 0 | if (load_status == 0) |
1028 | 0 | chunk_printf(&trash, "Success=0\n"); |
1029 | 0 | else |
1030 | 0 | chunk_printf(&trash, "Success=1\n"); |
1031 | |
|
1032 | 0 | if (startup_logs && ring_data(startup_logs) > 1) |
1033 | 0 | chunk_appendf(&trash, "--\n"); |
1034 | |
|
1035 | 0 | if (applet_putchk(appctx, &trash) == -1) |
1036 | 0 | return 0; |
1037 | | |
1038 | 0 | if (startup_logs) { |
1039 | 0 | appctx->cli_ctx.io_handler = NULL; |
1040 | 0 | ring_attach_cli(startup_logs, appctx, 0); |
1041 | 0 | return 0; |
1042 | 0 | } |
1043 | 0 | return 1; |
1044 | 0 | } |
1045 | | |
1046 | | static int mworker_parse_global_max_reloads(char **args, int section_type, struct proxy *curpx, |
1047 | | const struct proxy *defpx, const char *file, int linenum, char **err) |
1048 | 0 | { |
1049 | 0 | if (!(global.mode & MODE_DISCOVERY)) |
1050 | 0 | return 0; |
1051 | | |
1052 | 0 | if (strcmp(args[0], "mworker-max-reloads") == 0) { |
1053 | 0 | if (too_many_args(1, args, err, NULL)) |
1054 | 0 | return -1; |
1055 | | |
1056 | 0 | if (*(args[1]) == 0) { |
1057 | 0 | memprintf(err, "'%s' expects an integer argument.", args[0]); |
1058 | 0 | return -1; |
1059 | 0 | } |
1060 | | |
1061 | 0 | max_reloads = atol(args[1]); |
1062 | 0 | if (max_reloads < 0) { |
1063 | 0 | memprintf(err, "'%s' expects a positive value or zero.", args[0]); |
1064 | 0 | return -1; |
1065 | 0 | } |
1066 | 0 | } else { |
1067 | 0 | BUG_ON(1, "Triggered in mworker_parse_global_max_reloads() by unsupported keyword."); |
1068 | 0 | return -1; |
1069 | 0 | } |
1070 | | |
1071 | 0 | return 0; |
1072 | 0 | } |
1073 | | |
1074 | | void mworker_free_child(struct mworker_proc *child) |
1075 | 0 | { |
1076 | 0 | int i; |
1077 | |
|
1078 | 0 | if (child == NULL) |
1079 | 0 | return; |
1080 | | |
1081 | 0 | for (i = 0; child->command && child->command[i]; i++) |
1082 | 0 | ha_free(&child->command[i]); |
1083 | |
|
1084 | 0 | ha_free(&child->command); |
1085 | 0 | ha_free(&child->id); |
1086 | 0 | ha_free(&child->version); |
1087 | 0 | free(child); |
1088 | 0 | } |
1089 | | |
1090 | | /* Creates and binds dedicated master CLI 'reload' sockpair and listeners */ |
1091 | | void mworker_create_master_cli(void) |
1092 | 0 | { |
1093 | 0 | struct wordlist *it, *c; |
1094 | |
|
1095 | 0 | if (!LIST_ISEMPTY(&mworker_cli_conf)) { |
1096 | 0 | char *path = NULL; |
1097 | |
|
1098 | 0 | list_for_each_entry_safe(c, it, &mworker_cli_conf, list) { |
1099 | 0 | if (mworker_cli_master_proxy_new_listener(c->s) == NULL) { |
1100 | 0 | ha_alert("Can't create the master's CLI.\n"); |
1101 | 0 | exit(EXIT_FAILURE); |
1102 | 0 | } |
1103 | 0 | LIST_DELETE(&c->list); |
1104 | 0 | free(c->s); |
1105 | 0 | free(c); |
1106 | 0 | } |
1107 | | /* Creates the mcli_reload listener, which is the listener used |
1108 | | * to retrieve the master CLI session which asked for the reload. |
1109 | | * |
1110 | | * ipc_fd[1] will be used as a listener, and ipc_fd[0] |
1111 | | * will be used to send the FD of the session. |
1112 | | * |
1113 | | * Both FDs will be kept in the master. The sockets are |
1114 | | * created only if they weren't inherited. |
1115 | | */ |
1116 | 0 | if (proc_self->ipc_fd[1] == -1) { |
1117 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, proc_self->ipc_fd) < 0) { |
1118 | 0 | ha_alert("Can't create the mcli_reload socketpair.\n"); |
1119 | 0 | exit(EXIT_FAILURE); |
1120 | 0 | } |
1121 | 0 | } |
1122 | | |
1123 | | /* Create the mcli_reload listener from the proc_self struct */ |
1124 | 0 | memprintf(&path, "sockpair@%d", proc_self->ipc_fd[1]); |
1125 | |
|
1126 | 0 | mcli_reload_bind_conf = mworker_cli_master_proxy_new_listener(path); |
1127 | 0 | if (mcli_reload_bind_conf == NULL) { |
1128 | 0 | ha_alert("Can't create the mcli_reload listener.\n"); |
1129 | 0 | exit(EXIT_FAILURE); |
1130 | 0 | } |
1131 | 0 | ha_free(&path); |
1132 | 0 | } |
1133 | 0 | } |
1134 | | |
1135 | | /* This function fills proc_list for master-worker mode and creates a sockpair, |
1136 | | * copied after master-worker fork() to each process context to enable master |
1137 | | * CLI at worker side (worker can send its status to master).It only returns if |
1138 | | * everything is OK. If something fails, it exits. |
1139 | | */ |
1140 | | void mworker_prepare_master(void) |
1141 | 0 | { |
1142 | 0 | struct mworker_proc *tmproc; |
1143 | |
|
1144 | 0 | setenv("HAPROXY_MWORKER", "1", 1); |
1145 | |
|
1146 | 0 | if (getenv("HAPROXY_MWORKER_REEXEC") == NULL) { |
1147 | |
|
1148 | 0 | tmproc = mworker_proc_new(); |
1149 | 0 | if (!tmproc) { |
1150 | 0 | ha_alert("Cannot allocate process structures.\n"); |
1151 | 0 | exit(EXIT_FAILURE); |
1152 | 0 | } |
1153 | 0 | tmproc->options |= PROC_O_TYPE_MASTER; /* master */ |
1154 | 0 | tmproc->pid = pid; |
1155 | 0 | tmproc->timestamp = start_date.tv_sec; |
1156 | 0 | proc_self = tmproc; |
1157 | |
|
1158 | 0 | LIST_APPEND(&proc_list, &tmproc->list); |
1159 | 0 | } |
1160 | | |
1161 | 0 | tmproc = mworker_proc_new(); |
1162 | 0 | if (!tmproc) { |
1163 | 0 | ha_alert("Cannot allocate process structures.\n"); |
1164 | 0 | exit(EXIT_FAILURE); |
1165 | 0 | } |
1166 | | /* worker */ |
1167 | 0 | tmproc->options |= (PROC_O_TYPE_WORKER | PROC_O_INIT); |
1168 | | |
1169 | | /* create a sockpair to copy it via fork(), thus it will be in |
1170 | | * master and in worker processes |
1171 | | */ |
1172 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, tmproc->ipc_fd) < 0) { |
1173 | 0 | ha_alert("Cannot create worker master CLI socketpair.\n"); |
1174 | 0 | exit(EXIT_FAILURE); |
1175 | 0 | } |
1176 | 0 | LIST_APPEND(&proc_list, &tmproc->list); |
1177 | 0 | } |
1178 | | |
1179 | | static void mworker_loop() |
1180 | 0 | { |
1181 | | |
1182 | | /* Busy polling makes no sense in the master :-) */ |
1183 | 0 | global.tune.options &= ~GTUNE_BUSY_POLLING; |
1184 | |
|
1185 | 0 | mworker_unblock_sigchld(); |
1186 | 0 | mworker_cleantasks(); |
1187 | |
|
1188 | 0 | mworker_catch_sigchld(NULL); /* ensure we clean the children in case |
1189 | | some SIGCHLD were lost */ |
1190 | |
|
1191 | 0 | jobs++; /* this is the "master" job, we want to take care of the |
1192 | | signals even if there is no listener so the poll loop don't |
1193 | | leave */ |
1194 | |
|
1195 | 0 | fork_poller(); |
1196 | 0 | run_thread_poll_loop(NULL); |
1197 | 0 | } |
1198 | | |
1199 | | void mworker_run_master(void) |
1200 | 0 | { |
1201 | 0 | struct mworker_proc *child, *it; |
1202 | |
|
1203 | 0 | close(devnullfd); |
1204 | 0 | devnullfd = -1; |
1205 | |
|
1206 | 0 | proc_self->failedreloads = 0; /* reset the number of failure */ |
1207 | 0 | mworker_loop(); |
1208 | | #if defined(USE_OPENSSL) && !defined(OPENSSL_NO_DH) |
1209 | | ssl_free_dh(); |
1210 | | #endif |
1211 | 0 | master = 0; |
1212 | | /* close useless master sockets */ |
1213 | 0 | mworker_cli_proxy_stop(); |
1214 | | |
1215 | | /* free proc struct of other processes */ |
1216 | 0 | list_for_each_entry_safe(child, it, &proc_list, list) { |
1217 | | /* close the FD of the master side for all |
1218 | | * workers, we don't need to close the worker |
1219 | | * side of other workers since it's done with |
1220 | | * the bind_proc */ |
1221 | 0 | if (child->ipc_fd[0] >= 0) { |
1222 | 0 | close(child->ipc_fd[0]); |
1223 | 0 | child->ipc_fd[0] = -1; |
1224 | 0 | } |
1225 | 0 | LIST_DELETE(&child->list); |
1226 | 0 | mworker_free_child(child); |
1227 | 0 | child = NULL; |
1228 | 0 | } |
1229 | | /* master must leave */ |
1230 | 0 | exit(0); |
1231 | 0 | } |
1232 | | |
1233 | | /* This function at first does master-worker fork. It creates then GLOBAL and |
1234 | | * MASTER proxies, allocates listeners for these proxies and binds a GLOBAL |
1235 | | * proxy listener in worker process on ipc_fd[1] and MASTER proxy listener |
1236 | | * in master process on ipc_fd[0]. ipc_fd[0] and ipc_fd[1] are the "ends" of the |
1237 | | * sockpair, created in prepare_master(). This sockpair is copied via fork to |
1238 | | * each process and serves as communication channel between master and worker |
1239 | | * (master CLI applet is attached in master process to MASTER proxy). This |
1240 | | * function returns only if everything is OK. If something fails, it exits. |
1241 | | */ |
1242 | | void mworker_apply_master_worker_mode(void) |
1243 | 0 | { |
1244 | 0 | int worker_pid; |
1245 | 0 | struct mworker_proc *child; |
1246 | 0 | char *sock_name = NULL; |
1247 | 0 | char *errmsg = NULL; |
1248 | |
|
1249 | 0 | worker_pid = fork(); |
1250 | 0 | switch (worker_pid) { |
1251 | 0 | case -1: |
1252 | 0 | ha_alert("[%s.main()] Cannot fork.\n", progname); |
1253 | |
|
1254 | 0 | exit(EXIT_FAILURE); |
1255 | 0 | case 0: |
1256 | 0 | if (daemon_fd[1] >= 0) { |
1257 | 0 | close(daemon_fd[1]); |
1258 | 0 | daemon_fd[1] = -1; |
1259 | 0 | } |
1260 | | |
1261 | | /* This one must not be exported, it's internal! */ |
1262 | 0 | unsetenv("HAPROXY_MWORKER_REEXEC"); |
1263 | 0 | ha_random_jump96(1); |
1264 | |
|
1265 | 0 | list_for_each_entry(child, &proc_list, list) { |
1266 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->options & PROC_O_INIT)) { |
1267 | 0 | close(child->ipc_fd[0]); |
1268 | 0 | child->ipc_fd[0] = -1; |
1269 | | /* proc_self needs to point to the new forked worker in |
1270 | | * worker's context, as it's dereferenced in |
1271 | | * mworker_sockpair_register_per_thread(), called for |
1272 | | * master and for worker. |
1273 | | */ |
1274 | 0 | proc_self = child; |
1275 | | /* attach listener to GLOBAL proxy on child->ipc_fd[1] */ |
1276 | 0 | if (mworker_cli_global_proxy_new_listener(child) < 0) |
1277 | 0 | exit(EXIT_FAILURE); |
1278 | | |
1279 | 0 | break; |
1280 | 0 | } |
1281 | | |
1282 | | /* need to close reload sockpair fds, inherited after master's execvp and fork(), |
1283 | | * we can't close these fds in master before the fork(), as ipc_fd[1] serves after |
1284 | | * the mworker_reexec to obtain the MCLI client connection fd, like this we can |
1285 | | * write to this connection fd the content of the startup_logs ring. |
1286 | | */ |
1287 | 0 | if (child->options & PROC_O_TYPE_MASTER) { |
1288 | 0 | if (child->ipc_fd[0] > 0) |
1289 | 0 | close(child->ipc_fd[0]); |
1290 | 0 | if (child->ipc_fd[1] > 0) |
1291 | 0 | close(child->ipc_fd[1]); |
1292 | 0 | } |
1293 | 0 | } |
1294 | 0 | break; |
1295 | 0 | default: |
1296 | | /* in parent */ |
1297 | 0 | ha_notice("Initializing new worker (%d)\n", worker_pid); |
1298 | 0 | master = 1; |
1299 | | |
1300 | | /* in exec mode, there's always exactly one thread. Failure to |
1301 | | * set these ones now will result in nbthread being detected |
1302 | | * automatically. |
1303 | | */ |
1304 | 0 | global.nbtgroups = 1; |
1305 | 0 | global.nbthread = 1; |
1306 | | |
1307 | | /* creates MASTER proxy */ |
1308 | 0 | if (mworker_cli_create_master_proxy(&errmsg) < 0) { |
1309 | 0 | ha_alert("Can't create MASTER proxy: %s\n", errmsg); |
1310 | 0 | free(errmsg); |
1311 | 0 | exit(EXIT_FAILURE); |
1312 | 0 | } |
1313 | | |
1314 | | /* attaches servers to all existed workers on its shared MCLI sockpair ends, ipc_fd[0] */ |
1315 | 0 | if (mworker_cli_attach_server(&errmsg) < 0) { |
1316 | 0 | ha_alert("Can't attach servers needed for master CLI %s\n", errmsg ? errmsg : ""); |
1317 | 0 | free(errmsg); |
1318 | 0 | exit(EXIT_FAILURE); |
1319 | 0 | } |
1320 | | |
1321 | | /* creates reload sockpair and listeners for master CLI (-S) */ |
1322 | 0 | mworker_create_master_cli(); |
1323 | | |
1324 | | /* find the right mworker_proc */ |
1325 | 0 | list_for_each_entry(child, &proc_list, list) { |
1326 | 0 | if ((child->options & PROC_O_TYPE_WORKER) && (child->options & PROC_O_INIT)) { |
1327 | 0 | child->timestamp = date.tv_sec; |
1328 | 0 | child->pid = worker_pid; |
1329 | 0 | child->version = strdup(haproxy_version); |
1330 | |
|
1331 | 0 | close(child->ipc_fd[1]); |
1332 | 0 | child->ipc_fd[1] = -1; |
1333 | | |
1334 | | /* attach listener to MASTER proxy on child->ipc_fd[0] */ |
1335 | 0 | memprintf(&sock_name, "sockpair@%d", child->ipc_fd[0]); |
1336 | 0 | if (mworker_cli_master_proxy_new_listener(sock_name) == NULL) { |
1337 | 0 | ha_free(&sock_name); |
1338 | 0 | exit(EXIT_FAILURE); |
1339 | 0 | } |
1340 | 0 | ha_free(&sock_name); |
1341 | |
|
1342 | 0 | break; |
1343 | 0 | } |
1344 | 0 | } |
1345 | 0 | } |
1346 | 0 | } |
1347 | | |
1348 | | static struct cfg_kw_list mworker_kws = {{ }, { |
1349 | | { CFG_GLOBAL, "mworker-max-reloads", mworker_parse_global_max_reloads, KWF_DISCOVERY }, |
1350 | | { 0, NULL, NULL }, |
1351 | | }}; |
1352 | | |
1353 | | INITCALL1(STG_REGISTER, cfg_register_keywords, &mworker_kws); |
1354 | | |
1355 | | |
1356 | | /* register cli keywords */ |
1357 | | static struct cli_kw_list cli_kws = {{ },{ |
1358 | | { { "@<relative pid>", NULL }, "@<relative pid> : send a command to the <relative pid> process", NULL, cli_io_handler_show_proc, NULL, NULL, ACCESS_MASTER_ONLY}, |
1359 | | { { "@!<pid>", NULL }, "@!<pid> : send a command to the <pid> process", cli_parse_default, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1360 | | { { "@master", NULL }, "@master : send a command to the master process", cli_parse_default, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1361 | | { { "show", "proc", NULL }, "show proc : show processes status", cli_parse_show_proc, cli_io_handler_show_proc, NULL, NULL, ACCESS_MASTER_ONLY}, |
1362 | | { { "reload", NULL }, "reload : achieve a soft-reload (-sf) of haproxy", cli_parse_reload, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1363 | | { { "hard-reload", NULL }, "hard-reload : achieve a hard-reload (-st) of haproxy", cli_parse_reload, NULL, NULL, NULL, ACCESS_MASTER_ONLY}, |
1364 | | { { "_loadstatus", NULL }, NULL, cli_parse_default, cli_io_handler_show_loadstatus, NULL, NULL, ACCESS_MASTER_ONLY}, |
1365 | | {{},} |
1366 | | }}; |
1367 | | |
1368 | | INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); |