diff options
Diffstat (limited to 'src/bitcoind.cpp')
-rw-r--r-- | src/bitcoind.cpp | 144 |
1 files changed, 126 insertions, 18 deletions
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index b7bcb534ef..cf9e4fad44 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -12,26 +12,101 @@ #include <compat.h> #include <init.h> #include <interfaces/chain.h> +#include <interfaces/init.h> #include <node/context.h> #include <node/ui_interface.h> #include <noui.h> #include <shutdown.h> -#include <util/ref.h> +#include <util/check.h> #include <util/strencodings.h> #include <util/system.h> #include <util/threadnames.h> +#include <util/tokenpipe.h> #include <util/translation.h> #include <util/url.h> +#include <any> #include <functional> +#include <optional> const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; UrlDecodeFn* const URL_DECODE = urlDecode; -static bool AppInit(int argc, char* argv[]) +#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) { - NodeContext node; + // 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(NodeContext& node, int argc, char* argv[]) +{ bool fRet = false; util::ThreadSetInternalName("init"); @@ -59,7 +134,15 @@ static bool AppInit(int argc, char* argv[]) return true; } - util::Ref context{node}; +#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 + std::any context{&node}; try { if (!CheckDataDirOption()) { @@ -105,24 +188,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()) @@ -130,7 +223,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitInterfaces(node) && AppInitMain(context, node); + fRet = AppInitInterfaces(node) && AppInitMain(node); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -138,6 +231,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(); } @@ -153,10 +253,18 @@ int main(int argc, char* argv[]) util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); #endif + + NodeContext node; + int exit_status; + std::unique_ptr<interfaces::Init> init = interfaces::MakeNodeInit(node, argc, argv, exit_status); + if (!init) { + return exit_status; + } + SetupEnvironment(); // Connect bitcoind signal handlers noui_connect(); - return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); + return (AppInit(node, argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } |