aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build_msvc/bitcoin_config.h8
-rw-r--r--configure.ac5
-rw-r--r--src/bitcoind.cpp123
-rw-r--r--src/init.cpp5
-rw-r--r--src/init.h5
5 files changed, 128 insertions, 18 deletions
diff --git a/build_msvc/bitcoin_config.h b/build_msvc/bitcoin_config.h
index 40a30b9749..dd01cb29eb 100644
--- a/build_msvc/bitcoin_config.h
+++ b/build_msvc/bitcoin_config.h
@@ -92,9 +92,9 @@
don't. */
#define HAVE_DECL_BSWAP_64 0
-/* Define to 1 if you have the declaration of `daemon', and to 0 if you don't.
+/* Define to 1 if you have the declaration of `fork', and to 0 if you don't.
*/
-#define HAVE_DECL_DAEMON 0
+#define HAVE_DECL_FORK 0
/* Define to 1 if you have the declaration of `htobe16', and to 0 if you
don't. */
@@ -132,6 +132,10 @@
don't. */
#define HAVE_DECL_LE64TOH 0
+/* Define to 1 if you have the declaration of `setsid', and to 0 if you don't.
+ */
+#define HAVE_DECL_SETSID 0
+
/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you
don't. */
#define HAVE_DECL_STRERROR_R 0
diff --git a/configure.ac b/configure.ac
index d99f5dd8ae..3b318cb2df 100644
--- a/configure.ac
+++ b/configure.ac
@@ -922,8 +922,9 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],,,
)
AC_CHECK_DECLS([strnlen])
-dnl Check for daemon(3), unrelated to --with-daemon (although used by it)
-AC_CHECK_DECLS([daemon])
+dnl These are used for daemonization in bitcoind
+AC_CHECK_DECLS([fork])
+AC_CHECK_DECLS([setsid])
AC_CHECK_DECLS([pipe2])
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();
}
diff --git a/src/init.cpp b/src/init.cpp
index befba2eb2d..8adb637d6e 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -577,8 +577,9 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
-#if HAVE_DECL_DAEMON
- argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+#if HAVE_DECL_FORK
+ argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
+ argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-daemon");
#endif
diff --git a/src/init.h b/src/init.h
index c04d966d06..34bca09dd1 100644
--- a/src/init.h
+++ b/src/init.h
@@ -9,6 +9,11 @@
#include <memory>
#include <string>
+//! Default value for -daemon option
+static constexpr bool DEFAULT_DAEMON = false;
+//! Default value for -daemonwait option
+static constexpr bool DEFAULT_DAEMONWAIT = false;
+
class ArgsManager;
struct NodeContext;
namespace interfaces {