aboutsummaryrefslogtreecommitdiff
path: root/src/bitcoind.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bitcoind.cpp')
-rw-r--r--src/bitcoind.cpp123
1 files changed, 111 insertions, 12 deletions
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index b7bcb534ef..32f06aec2c 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -20,6 +20,7 @@
#include <util/strencodings.h>
#include <util/system.h>
#include <util/threadnames.h>
+#include <util/tokenpipe.h>
#include <util/translation.h>
#include <util/url.h>
@@ -28,6 +29,79 @@
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
UrlDecodeFn* const URL_DECODE = urlDecode;
+#if HAVE_DECL_FORK
+
+/** Custom implementation of daemon(). This implements the same order of operations as glibc.
+ * Opens a pipe to the child process to be able to wait for an event to occur.
+ *
+ * @returns 0 if successful, and in child process.
+ * >0 if successful, and in parent process.
+ * -1 in case of error (in parent process).
+ *
+ * In case of success, endpoint will be one end of a pipe from the child to parent process,
+ * which can be used with TokenWrite (in the child) or TokenRead (in the parent).
+ */
+int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
+{
+ // communication pipe with child process
+ std::optional<TokenPipe> umbilical = TokenPipe::Make();
+ if (!umbilical) {
+ return -1; // pipe or pipe2 failed.
+ }
+
+ int pid = fork();
+ if (pid < 0) {
+ return -1; // fork failed.
+ }
+ if (pid != 0) {
+ // Parent process gets read end, closes write end.
+ endpoint = umbilical->TakeReadEnd();
+ umbilical->TakeWriteEnd().Close();
+
+ int status = endpoint.TokenRead();
+ if (status != 0) { // Something went wrong while setting up child process.
+ endpoint.Close();
+ return -1;
+ }
+
+ return pid;
+ }
+ // Child process gets write end, closes read end.
+ endpoint = umbilical->TakeWriteEnd();
+ umbilical->TakeReadEnd().Close();
+
+#if HAVE_DECL_SETSID
+ if (setsid() < 0) {
+ exit(1); // setsid failed.
+ }
+#endif
+
+ if (!nochdir) {
+ if (chdir("/") != 0) {
+ exit(1); // chdir failed.
+ }
+ }
+ if (!noclose) {
+ // Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach
+ // from terminal.
+ int fd = open("/dev/null", O_RDWR);
+ if (fd >= 0) {
+ bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0;
+ // Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open.
+ if (fd > 2) close(fd);
+ if (err) {
+ exit(1); // dup2 failed.
+ }
+ } else {
+ exit(1); // open /dev/null failed.
+ }
+ }
+ endpoint.TokenWrite(0); // Success
+ return 0;
+}
+
+#endif
+
static bool AppInit(int argc, char* argv[])
{
NodeContext node;
@@ -59,6 +133,14 @@ static bool AppInit(int argc, char* argv[])
return true;
}
+#if HAVE_DECL_FORK
+ // Communication with parent after daemonizing. This is used for signalling in the following ways:
+ // - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
+ // that the parent process can quit, and whether it was successful/unsuccessful.
+ // - an unexpected shutdown of the child process creates an unexpected end of stream at the parent
+ // end, which is interpreted as failure to start.
+ TokenPipeEnd daemon_ep;
+#endif
util::Ref context{node};
try
{
@@ -105,24 +187,34 @@ static bool AppInit(int argc, char* argv[])
// InitError will have been called with detailed error, which ends up on console
return false;
}
- if (args.GetBoolArg("-daemon", false)) {
-#if HAVE_DECL_DAEMON
-#if defined(MAC_OSX)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
+ if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
+#if HAVE_DECL_FORK
tfm::format(std::cout, PACKAGE_NAME " starting\n");
// Daemonize
- if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
- return InitError(Untranslated(strprintf("daemon() failed: %s\n", strerror(errno))));
+ switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0)
+ case 0: // Child: continue.
+ // If -daemonwait is not enabled, immediately send a success token the parent.
+ if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
+ daemon_ep.TokenWrite(1);
+ daemon_ep.Close();
+ }
+ break;
+ case -1: // Error happened.
+ return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno))));
+ default: { // Parent: wait and exit.
+ int token = daemon_ep.TokenRead();
+ if (token) { // Success
+ exit(EXIT_SUCCESS);
+ } else { // fRet = false or token read error (premature exit).
+ tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n");
+ exit(EXIT_FAILURE);
+ }
+ }
}
-#if defined(MAC_OSX)
-#pragma GCC diagnostic pop
-#endif
#else
return InitError(Untranslated("-daemon is not supported on this operating system\n"));
-#endif // HAVE_DECL_DAEMON
+#endif // HAVE_DECL_FORK
}
// Lock data directory after daemonization
if (!AppInitLockDataDirectory())
@@ -138,6 +230,13 @@ static bool AppInit(int argc, char* argv[])
PrintExceptionContinue(nullptr, "AppInit()");
}
+#if HAVE_DECL_FORK
+ if (daemon_ep.IsOpen()) {
+ // Signal initialization status to parent, then close pipe.
+ daemon_ep.TokenWrite(fRet);
+ daemon_ep.Close();
+ }
+#endif
if (fRet) {
WaitForShutdown();
}