From 36cbc7e536e265da8f45e230739fca635760512e Mon Sep 17 00:00:00 2001 From: Valentine Krasnobaeva Date: Mon, 9 Dec 2024 18:51:34 +0100 Subject: [PATCH] BUG/MEDIUM: startup: report status if daemonized process fails Due to master-worker rework, daemonization fork happens now before parsing and applying the configuration. This makes impossible to report correctly all warnings and alerts to shell's stdout. Daemonzied process fails, while being already in background, exit code reported by shell via '$?' equals to 0, as it's the exit code of his parent. To fix this, let's create a pipe between parent and daemonized child. The child will send into this pipe a "READY" message, when it finishes his initialization. The parent will wait on the "read" end of the pipe until receiving something. If read() fails, parent obtains the status of the exited child with waitpid(). So, the parent can correctly report the error to the stdout and he can exit with child's exitcode. This fix should be backported only in 3.1. (cherry picked from commit 663d75e7a00a9c50185cca322cbc64de934b6631) Signed-off-by: Willy Tarreau --- include/haproxy/global.h | 1 + src/haproxy.c | 62 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/include/haproxy/global.h b/include/haproxy/global.h index 3c09968..a5730bc 100644 --- a/include/haproxy/global.h +++ b/include/haproxy/global.h @@ -53,6 +53,7 @@ extern char **init_env; extern char *progname; extern char **old_argv; extern const char *old_unixsocket; +extern int daemon_fd[2]; struct proxy; struct server; diff --git a/src/haproxy.c b/src/haproxy.c index 1d51c97..1581a9e 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -155,6 +155,7 @@ static struct list cfg_cfgfiles = LIST_HEAD_INIT(cfg_cfgfiles); int pid; /* current process id */ char **init_env; /* to keep current process env variables backup */ int pidfd = -1; /* FD to keep PID */ +int daemon_fd[2] = {-1, -1}; /* pipe to communicate with parent process */ static unsigned long stopping_tgroup_mask; /* Thread groups acknowledging stopping */ @@ -1756,6 +1757,17 @@ static void generate_random_cluster_secret() static void apply_daemon_mode() { int ret; + int wstatus = 0; + int exitcode = 0; + pid_t child_pid; + char buf[2]; + + if (pipe(daemon_fd) < 0) { + ha_alert("[%s.main()] Cannot create pipe for getting the status of " + "child process: %s.\n", progname, strerror(errno)); + + exit(EXIT_FAILURE); + } ret = fork(); switch(ret) { @@ -1767,12 +1779,44 @@ static void apply_daemon_mode() /* in child, change the process group ID, in the master-worker * mode, this will be the master process */ + close(daemon_fd[0]); + daemon_fd[0] = -1; setsid(); break; default: - /* in parent, which leaves to daemonize */ - exit(0); + /* in parent */ + close(daemon_fd[1]); + daemon_fd[1] = -1; + /* In standalone + daemon modes: parent (launcher process) tries + * to read the child's (daemonized process) "READY" message. Child + * writes this message, when he has finished initialization. If + * child failed to start, we get his status. + * In master-worker mode: daemonized process is the master. He + * sends his READY message to launcher, only when + * he has received the READY message from the worker, see + * _send_status(). + */ + if (read(daemon_fd[0], buf, 1) == 0) { + child_pid = waitpid(ret, &wstatus, 0); + if (child_pid < 0) { + ha_alert("[%s.main()] waitpid() failed: %s\n", + progname, strerror(errno)); + exit(EXIT_FAILURE); + } + if (WIFEXITED(wstatus)) + wstatus = WEXITSTATUS(wstatus); + else if (WIFSIGNALED(wstatus)) + wstatus = 128 + WTERMSIG(wstatus); + else + wstatus = 255; + + ha_alert("Process %d exited with code %d (%s)\n", + child_pid, wstatus, (wstatus >= 128) ? strsignal(wstatus - 128) : "Exit"); + if (wstatus != 0 && wstatus != 143) + exitcode = wstatus; + } + exit(exitcode); } } @@ -3248,6 +3292,7 @@ int main(int argc, char **argv) struct cfgfile *cfg, *cfg_tmp; struct ring *tmp_startup_logs = NULL; struct mworker_proc *proc; + char *msg = "READY\n"; /* Catch broken toolchains */ if (sizeof(long) != sizeof(void *) || (intovf + 0x7FFFFFFF >= intovf)) { @@ -3749,6 +3794,19 @@ int main(int argc, char **argv) startup_logs_free(startup_logs); startup_logs = tmp_startup_logs; } + + /* worker is already sent its READY message to master. This applies only + * for daemon standalone mode. Master in daemon mode will "forward" the READY + * message received from the worker to the launching process, see _send_status(). + */ + if ((global.mode & MODE_DAEMON) && !(global.mode & MODE_MWORKER)) { + if (write(daemon_fd[1], msg, strlen(msg)) < 0) { + ha_alert("[%s.main()] Failed to write into pipe with parent process: %s\n", progname, strerror(errno)); + exit(1); + } + close(daemon_fd[1]); + daemon_fd[1] = -1; + } /* can't unset MODE_STARTING earlier, otherwise worker's last alerts * should be not written in startup logs. */ -- 1.7.10.4