]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: startup: report status if daemonized process fails
authorValentine Krasnobaeva <vkrasnobaeva@haproxy.com>
Mon, 9 Dec 2024 17:51:34 +0000 (18:51 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 9 Dec 2024 20:32:44 +0000 (21:32 +0100)
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.

include/haproxy/global.h
src/haproxy.c

index 3c09968c5b68f5b86dc915f5824c54bbc3091766..a5730bc4b6ba207275ccec09db811d9183673c9b 100644 (file)
@@ -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;
index 94faab41cb117c3ddd69f837f87fa77b85bc7d9a..7b03e9ed89928916ad1ac3bc9a1ce86ef81508fd 100644 (file)
@@ -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)) {
@@ -3754,6 +3799,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.
         */