From be47320a0589a84eb942790c3884bb6e25c38a2f Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Sat, 18 Jan 2014 12:01:09 +0100
Subject: [PATCH] Daemon: fork as early as possible

Keep the parent process around until MPD has finished initializing.

This is important for libraries that are allergic to fork(), such as
libupnp.
---
 src/Daemon.cxx  | 101 ++++++++++++++++++++++++++++++++++++------------
 src/Daemon.hxx  |  12 +++++-
 src/Main.cxx    |   3 +-
 src/PidFile.hxx |  19 +++++++++
 4 files changed, 107 insertions(+), 28 deletions(-)

diff --git a/src/Daemon.cxx b/src/Daemon.cxx
index f11a6e547..490b2def5 100644
--- a/src/Daemon.cxx
+++ b/src/Daemon.cxx
@@ -31,6 +31,7 @@
 #include <fcntl.h>
 
 #ifndef WIN32
+#include <sys/wait.h>
 #include <signal.h>
 #include <pwd.h>
 #include <grp.h>
@@ -55,6 +56,11 @@ static AllocatedPath pidfile = AllocatedPath::Null();
 /* whether "group" conf. option was given */
 static bool had_group = false;
 
+/**
+ * The write end of a pipe that is used to notify the parent process
+ * that initialization has finished and that it should detach.
+ */
+static int detach_fd = -1;
 
 void
 daemonize_kill(void)
@@ -130,48 +136,93 @@ daemonize_set_user(void)
 	}
 }
 
-static void
-daemonize_detach(void)
+void
+daemonize_begin(bool detach)
 {
+	/* release the current working directory */
+	if (chdir("/") < 0)
+		FatalError("problems changing to root directory");
+
+	if (!detach)
+		/* the rest of this function deals with detaching the
+		   process */
+		return;
+
+	/* do this before daemonizing so we can fail gracefully if we
+	   can't write to the pid file */
+	PidFile pidfile2(pidfile);
+
 	/* flush all file handles before duplicating the buffers */
 
 	fflush(nullptr);
 
-	/* detach from parent process */
+	/* create a pipe to synchronize the parent and the child */
+
+	int fds[2];
+	if (pipe(fds) < 0)
+		FatalSystemError("pipe() failed");
 
-	switch (fork()) {
-	case -1:
+	/* move to a child process */
+
+	pid_t pid = fork();
+	if (pid < 0)
 		FatalSystemError("fork() failed");
-	case 0:
-		break;
-	default:
-		/* exit the parent process */
-		_exit(EXIT_SUCCESS);
+
+	if (pid == 0) {
+		/* in the child process */
+
+		pidfile2.Close();
+		close(fds[0]);
+		detach_fd = fds[1];
+
+		/* detach from the current session */
+		setsid();
+
+		/* continue starting MPD */
+		return;
 	}
 
-	/* release the current working directory */
+	/* in the parent process */
 
-	if (chdir("/") < 0)
-		FatalError("problems changing to root directory");
+	close(fds[1]);
+
+	int result;
+	ssize_t nbytes = read(fds[0], &result, sizeof(result));
+	if (nbytes == (ssize_t)sizeof(result)) {
+		/* the child process was successful */
+		pidfile2.Write(pid);
+		exit(EXIT_SUCCESS);
+	}
+
+	/* something bad happened in the child process */
 
-	/* detach from the current session */
+	pidfile2.Delete(pidfile);
 
-	setsid();
+	int status;
+	pid_t pid2 = waitpid(pid, &status, 0);
+	if (pid2 < 0)
+		FatalSystemError("waitpid() failed");
 
-	LogDebug(daemon_domain, "daemonized");
+	if (WIFSIGNALED(status))
+		FormatFatalError("MPD died from signal %d%s", WTERMSIG(status),
+				 WCOREDUMP(status) ? " (core dumped)" : "");
+
+	exit(WEXITSTATUS(status));
 }
 
 void
-daemonize(bool detach)
+daemonize_commit()
 {
-	/* do this before daemon'izing so we can fail gracefully if we
-	   can't write to the pid file */
-	PidFile pidfile2(pidfile);
-
-	if (detach)
-		daemonize_detach();
-
-	pidfile2.Write();
+	if (detach_fd >= 0) {
+		/* tell the parent process to let go of us and exit
+		   indicating success */
+		int result = 0;
+		write(detach_fd, &result, sizeof(result));
+		close(detach_fd);
+	} else
+		/* the pidfile was not written by the parent because
+		   there is no parent - do it now */
+		PidFile(pidfile).Write();
 }
 
 void
diff --git a/src/Daemon.hxx b/src/Daemon.hxx
index d1ce7d0f8..fe5681511 100644
--- a/src/Daemon.hxx
+++ b/src/Daemon.hxx
@@ -81,11 +81,19 @@ daemonize_set_user(void)
 
 #ifndef WIN32
 void
-daemonize(bool detach);
+daemonize_begin(bool detach);
 #else
 static inline void
-daemonize(bool detach)
+daemonize_begin(bool detach)
 { (void)detach; }
 #endif
 
+#ifndef WIN32
+void
+daemonize_commit();
+#else
+static inline void
+daemonize_commit() {}
+#endif
+
 #endif
diff --git a/src/Main.cxx b/src/Main.cxx
index bc5667755..9cbefdc56 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -403,6 +403,7 @@ int mpd_main(int argc, char *argv[])
 	}
 
 	daemonize_set_user();
+	daemonize_begin(options.daemon);
 
 	GlobalEvents::Initialize(*main_loop);
 	GlobalEvents::Register(GlobalEvents::IDLE, idle_event_emitted);
@@ -451,7 +452,7 @@ int mpd_main(int argc, char *argv[])
 
 	playlist_list_global_init();
 
-	daemonize(options.daemon);
+	daemonize_commit();
 
 	setup_log_output(options.log_stderr);
 
diff --git a/src/PidFile.hxx b/src/PidFile.hxx
index c06ed6b61..a242c7810 100644
--- a/src/PidFile.hxx
+++ b/src/PidFile.hxx
@@ -47,6 +47,25 @@ public:
 
 	PidFile(const PidFile &) = delete;
 
+	void Close() {
+		if (file == nullptr)
+			return;
+
+		fclose(file);
+	}
+
+	void Delete(const AllocatedPath &path) {
+		if (file == nullptr) {
+			assert(path.IsNull());
+			return;
+		}
+
+		assert(!path.IsNull());
+
+		fclose(file);
+		RemoveFile(path);
+	}
+
 	void Write(pid_t pid) {
 		if (file == nullptr)
 			return;
-- 
2.24.1