diff --git a/configure.ac b/configure.ac index b73dbddeb6..741b1bd618 100755 --- a/configure.ac +++ b/configure.ac @@ -685,8 +685,11 @@ AC_CHECK_HEADERS([endian.h sys/endian.h byteswap.h stdio.h stdlib.h unistd.h str AC_CHECK_DECLS([strnlen]) -# 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]) AC_CHECK_DECLS([le16toh, le32toh, le64toh, htole16, htole32, htole64, be16toh, be32toh, be64toh, htobe16, htobe32, htobe64],,, [#if HAVE_ENDIAN_H diff --git a/contrib/init/gridcoinresearchd.service b/contrib/init/gridcoinresearchd.service new file mode 100644 index 0000000000..5077f85301 --- /dev/null +++ b/contrib/init/gridcoinresearchd.service @@ -0,0 +1,82 @@ +# It is not recommended to modify this file in-place, because it will +# be overwritten during package upgrades. If you want to add further +# options or overwrite existing ones then use +# $ systemctl edit gridcoinresearchd.service +# See "man systemd.service" for details. + +# Note that almost all daemon options could be specified in +# /etc/gridcoin/gridcoin.conf, but keep in mind those explicitly +# specified as arguments in ExecStart= will override those in the +# config file. + +[Unit] +Description=Gridcoin daemon +Documentation=https://github.com/gridcoin-community/Gridcoin-Research/blob/development/doc/gridcoinresearch.conf.md + +# https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/ +After=network-online.target +Wants=network-online.target + +[Service] +ExecStart=/usr/bin/gridcoinresearchd -daemonwait \ + -pid=/run/gridcoinresearchd/gridcoinresearchd.pid \ + -conf=/etc/gridcoin/gridcoin.conf \ + -datadir=/var/lib/gridcoinresearchd + +# Make sure the config directory is readable by the service user +PermissionsStartOnly=true +ExecStartPre=/bin/chgrp gridcoin /etc/gridcoin + +# Process management +#################### + +Type=forking +PIDFile=/run/gridcoinresearchd/gridcoinresearchd.pid +Restart=on-failure +TimeoutStartSec=infinity +TimeoutStopSec=600 + +# Directory creation and permissions +#################################### + +# Run as gridcoin:gridcoin +User=gridcoin +Group=gridcoin + +# /run/gridcoinresearchd +RuntimeDirectory=gridcoinresearchd +RuntimeDirectoryMode=0710 + +# /etc/gridcoin +ConfigurationDirectory=gridcoin +ConfigurationDirectoryMode=0710 + +# /var/lib/gridcoinresearchd +StateDirectory=gridcoinresearchd +StateDirectoryMode=0710 + +# Hardening measures +#################### + +# Provide a private /tmp and /var/tmp. +PrivateTmp=true + +# Mount /usr, /boot/ and /etc read-only for the process. +ProtectSystem=full + +# Deny access to /home, /root and /run/user +ProtectHome=true + +# Disallow the process and all of its children to gain +# new privileges through execve(). +NoNewPrivileges=true + +# Use a new /dev namespace only populated with API pseudo devices +# such as /dev/null, /dev/zero and /dev/random. +PrivateDevices=true + +# Deny the creation of writable and executable memory mappings. +MemoryDenyWriteExecute=true + +[Install] +WantedBy=multi-user.target diff --git a/src/Makefile.am b/src/Makefile.am index 0139eb6324..dbf5977f90 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -194,9 +194,11 @@ GRIDCOIN_CORE_H = \ util/settings.h \ util/strencodings.h \ util/string.h \ + util/syserror.h \ util/system.h \ util/threadnames.h \ util/time.h \ + util/tokenpipe.h \ util.h \ validation.h \ version.h \ @@ -296,9 +298,11 @@ GRIDCOIN_CORE_CPP = addrdb.cpp \ util/settings.cpp \ util/strencodings.cpp \ util/string.cpp \ + util/syserror.cpp \ util/system.cpp \ util/threadnames.cpp \ util/time.cpp \ + util/tokenpipe.cpp \ util.cpp \ validation.cpp \ wallet/db.cpp \ diff --git a/src/fs.cpp b/src/fs.cpp index a9ccd89d8f..e592e3aef4 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1,4 +1,9 @@ +// Copyright (c) 2017-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + #include +#include #ifndef WIN32 #include @@ -34,7 +39,7 @@ fs::path AbsPathJoin(const fs::path& base, const fs::path& path) #ifndef WIN32 static std::string GetErrorReason() { - return std::strerror(errno); + return SysErrorString(errno); } FileLock::FileLock(const fs::path& file) diff --git a/src/gridcoinresearchd.cpp b/src/gridcoinresearchd.cpp index ff174a6655..65ef60ce4f 100644 --- a/src/gridcoinresearchd.cpp +++ b/src/gridcoinresearchd.cpp @@ -10,7 +10,9 @@ #include "chainparams.h" #include "chainparamsbase.h" #include "util.h" +#include #include "util/threadnames.h" +#include #include "net.h" #include "txdb.h" #include "wallet/walletdb.h" @@ -26,10 +28,79 @@ extern bool fQtActive; -////////////////////////////////////////////////////////////////////////////// -// -// Start -// +#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 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 + bool AppInit(int argc, char* argv[]) { #ifdef WIN32 @@ -55,8 +126,7 @@ bool AppInit(int argc, char* argv[]) // If Qt is used, parameters/gridcoinresearch.conf are parsed in qt/bitcoin.cpp's main() std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); - return EXIT_FAILURE; + return InitError(strprintf("Error parsing command line arguments: %s\n", error)); } if (HelpRequested(gArgs)) { @@ -70,20 +140,27 @@ bool AppInit(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); tfm::format(std::cout, "%s", strUsage); - - return EXIT_SUCCESS; + return true; } if (gArgs.IsArgSet("-version")) { tfm::format(std::cout, "%s", VersionMessage().c_str()); - return false; + 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 + if (!CheckDataDirOption()) { - tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")); - return EXIT_FAILURE; + return InitError(strprintf("Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", ""))); } /** Check mainnet config file first in case testnet is set there and not in command line args **/ @@ -94,8 +171,7 @@ bool AppInit(int argc, char* argv[]) if (!gArgs.ReadConfigFiles(error_msg, true)) { - tfm::format(std::cerr, "Config file cannot be parsed. Cannot continue.\n"); - exit(1); + return InitError("Config file cannot be parsed. Cannot continue.\n"); } SelectParams(gArgs.IsArgSet("-testnet") ? CBaseChainParams::TESTNET : CBaseChainParams::MAIN); @@ -103,13 +179,11 @@ bool AppInit(int argc, char* argv[]) // reread config file after correct chain is selected if (!gArgs.ReadConfigFiles(error_msg, true)) { - tfm::format(std::cerr, "Config file cannot be parsed. Cannot continue.\n"); - exit(1); + return InitError("Config file cannot be parsed. Cannot continue.\n"); } if (!gArgs.InitSettings(error)) { - tfm::format(std::cerr, "Error initializing settings.\n"); - exit(1); + return InitError("Error initializing settings.\n"); } // Command-line RPC - single commands execute and exit. @@ -119,19 +193,18 @@ bool AppInit(int argc, char* argv[]) if (fCommandLine) { - int ret = CommandLineRPC(argc, argv); - exit(ret); + return !CommandLineRPC(argc, argv); } + // -server defaults to true for gridcoinresearchd but not for the GUI so do this here + gArgs.SoftSetBoolArg("-server", true); // Initialize logging as early as possible. InitLogging(); // Make sure a user does not request snapshotdownload and resetblockchaindata at same time! if (gArgs.IsArgSet("-snapshotdownload") && gArgs.IsArgSet("-resetblockchaindata")) { - tfm::format(std::cerr, "-snapshotdownload and -resetblockchaindata cannot be used in conjunction"); - - exit(1); + return InitError("-snapshotdownload and -resetblockchaindata cannot be used in conjunction"); } // Check to see if the user requested a snapshot and we are not running TestNet! @@ -143,10 +216,8 @@ bool AppInit(int argc, char* argv[]) // Use new probe feature if (!LockDirectory(GetDataDir(), ".lock", false)) { - tfm::format(std::cerr, "Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", - GetDataDir().string().c_str()); - - exit(1); + return InitError(strprintf("Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", + GetDataDir().string())); } else @@ -162,7 +233,7 @@ bool AppInit(int argc, char* argv[]) snapshot.DeleteSnapshot(); - exit(1); + return false; } } @@ -178,9 +249,8 @@ bool AppInit(int argc, char* argv[]) // Let's check make sure Gridcoin is not already running in the data directory. if (!LockDirectory(GetDataDir(), ".lock", false)) { - tfm::format(std::cerr, "Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", GetDataDir().string().c_str()); - - exit(1); + return InitError(strprintf("Cannot obtain a lock on data directory %s. Gridcoin is probably already running.", + GetDataDir().string())); } else @@ -192,18 +262,49 @@ bool AppInit(int argc, char* argv[]) { LogPrintf("ResetBlockchainData: failed to clean up blockchain data"); - std::string inftext = resetblockchain.ResetBlockchainMessages(resetblockchain.CleanUp); - - tfm::format(std::cerr, "%s", inftext.c_str()); - - exit(1); + return InitError(resetblockchain.ResetBlockchainMessages(resetblockchain.CleanUp)); } } } - LogPrintf("AppInit"); + if (gArgs.GetBoolArg("-daemon", DEFAULT_DAEMON) || gArgs.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { +#if HAVE_DECL_FORK + tfm::format(std::cout, PACKAGE_NAME " starting\n"); + + // Daemonize + 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 (!gArgs.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) { + daemon_ep.TokenWrite(1); + daemon_ep.Close(); + } + break; + case -1: // Error happened. + return InitError(strprintf("fork_daemon() failed: %s\n", SysErrorString(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); + } + } + } +#else + return InitError("-daemon is not supported on this operating system\n"); +#endif // HAVE_DECL_FORK + } fRet = AppInit2(threads); +#if HAVE_DECL_FORK + if (daemon_ep.IsOpen()) { + // Signal initialization status to parent, then close pipe. + daemon_ep.TokenWrite(fRet); + daemon_ep.Close(); + } +#endif } catch (std::exception& e) { LogPrintf("AppInit()Exception1"); @@ -238,18 +339,11 @@ int main(int argc, char* argv[]) // Reinit default timer to ensure it is zeroed out at the start of main. g_timer.InitTimer("default", false); - bool fRet = false; - // Set global boolean to indicate intended absence of GUI to core... fQtActive = false; // Connect bitcoind signal handlers noui_connect(); - fRet = AppInit(argc, argv); - - if (fRet && fDaemon) - return 0; - - return 1; + return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/init.cpp b/src/init.cpp index a1f35d740d..5d1b117799 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -20,6 +20,7 @@ #include "gridcoin/upgrade.h" #include "miner.h" #include "node/blockstorage.h" +#include #include #include @@ -67,6 +68,21 @@ static fs::path GetPidFile(const ArgsManager& args) return AbsPathForConfigVal(fs::path(args.GetArg("-pid", GRIDCOIN_PID_FILENAME))); } +[[nodiscard]] static bool CreatePidFile(const ArgsManager& args) +{ + fsbridge::ofstream file{GetPidFile(args)}; + if (file) { +#ifdef WIN32 + tfm::format(file, "%d\n", GetCurrentProcessId()); +#else + tfm::format(file, "%d\n", getpid()); +#endif + return true; + } else { + return InitError(strprintf(_("Unable to create the PID file '%s': %s"), GetPidFile(args).string(), SysErrorString(errno))); + } +} + ////////////////////////////////////////////////////////////////////////////// // // Shutdown @@ -320,8 +336,6 @@ void SetupServerArgs() ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mininput=", "When creating transactions, ignore inputs with value less than this (default: 0.01)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", - ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-testnet", "Use the test network", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocknotify=", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -560,6 +574,14 @@ void SetupServerArgs() "Acceptable ciphers (default: TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); +#if HAVE_DECL_FORK + argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#else + hidden_args.emplace_back("-daemon"); + hidden_args.emplace_back("-daemonwait"); +#endif + // Additional hidden options hidden_args.emplace_back("-devbuild"); hidden_args.emplace_back("-scrapersleep"); @@ -892,28 +914,10 @@ bool AppInit2(ThreadHandlerPtr threads) LogInstance().EnableCategory(BCLog::LogFlags::VERBOSE); } -#if defined(WIN32) - fDaemon = false; -#else - if(fQtActive) - fDaemon = false; - else - fDaemon = gArgs.GetBoolArg("-daemon"); -#endif - // Check for -socks - as this is a privacy risk to continue, exit here if (gArgs.IsArgSet("-socks")) return InitError(_("Error: Unsupported argument -socks found. Setting SOCKS version isn't possible anymore, only SOCKS5 proxies are supported.")); - if (fDaemon) - fServer = true; - else - fServer = gArgs.GetBoolArg("-server"); - - /* force fServer when running without GUI */ - if(!fQtActive) - fServer = true; - if (gArgs.IsArgSet("-timeout")) { int nNewTimeout = gArgs.GetArg("-timeout", 5000); @@ -949,6 +953,10 @@ bool AppInit2(ThreadHandlerPtr threads) } // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log + if (!CreatePidFile(gArgs)) { + // Detailed error printed inside CreatePidFile(). + return false; + } // Initialize internal hashing code with SSE/AVX2 optimizations. In the future we will also have ARM/NEON optimizations. std::string sha256_algo = SHA256AutoDetect(); LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo); @@ -981,33 +989,6 @@ bool AppInit2(ThreadHandlerPtr threads) return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), datadir.string(), PACKAGE_NAME)); } - -#if !defined(WIN32) - if (fDaemon) - { - // Daemonize - pid_t pid = fork(); - if (pid < 0) - { - tfm::format(std::cerr, "Error: fork() returned %d errno %d\n", pid, errno); - return false; - } - if (pid > 0) - { - CreatePidFile(GetPidFile(gArgs), pid); - - // Now that we are forked we can request a shutdown so the parent - // exits while the child lives on. - StartShutdown(); - return true; - } - - pid_t sid = setsid(); - if (sid < 0) - tfm::format(std::cerr, "Error: setsid() returned %d errno %d\n", sid, errno); - } -#endif - #if (OPENSSL_VERSION_NUMBER < 0x10100000L) LogPrintf("Using OpenSSL version %s\n", SSLeay_version(SSLEAY_VERSION)); #elif defined OPENSSL_VERSION @@ -1036,9 +1017,6 @@ bool AppInit2(ThreadHandlerPtr threads) } } - if (fDaemon) - tfm::format(std::cout, "Gridcoin server starting\n"); - // ********************************************************* Step 5: verify database integrity uiInterface.InitMessage(_("Verifying database integrity...")); @@ -1485,7 +1463,7 @@ bool AppInit2(ThreadHandlerPtr threads) if (!threads->createThread(StartNode, nullptr, "Start Thread")) InitError(_("Error: could not start node")); - if (fServer) StartRPCThreads(); + if (gArgs.GetBoolArg("-server", false)) StartRPCThreads(); // ********************************************************* Step 13: finished diff --git a/src/init.h b/src/init.h index a40e5f6fc2..db12638805 100644 --- a/src/init.h +++ b/src/init.h @@ -1,13 +1,19 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2009-2021 The Bitcoin developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. + #ifndef BITCOIN_INIT_H #define BITCOIN_INIT_H #include "wallet/wallet.h" #include +//! Default value for -daemon option +static constexpr bool DEFAULT_DAEMON = false; +//! Default value for -daemonwait option +static constexpr bool DEFAULT_DAEMONWAIT = false; + extern CWallet* pwalletMain; void InitLogging(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 3ab0d855ba..2866b4bb1b 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -145,7 +145,7 @@ static bool ThreadSafeAskFee(int64_t nFeeRequired, const std::string& strCaption nMinFee = GetBaseFee(txDummy, GMF_SEND); } - if(nFeeRequired < nMinFee || nFeeRequired <= nTransactionFee || fDaemon) + if (nFeeRequired < nMinFee || nFeeRequired <= nTransactionFee) return true; bool payFee = false; diff --git a/src/util.cpp b/src/util.cpp index 876f9b7ef0..d48c2f6709 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -29,8 +29,6 @@ using namespace std; bool fPrintToConsole = false; bool fRequestShutdown = false; std::atomic fShutdown = false; -bool fDaemon = false; -bool fServer = false; bool fCommandLine = false; bool fTestNet = false; bool fNoListen = false; @@ -170,17 +168,6 @@ bool WildcardMatch(const string& str, const string& mask) return WildcardMatch(str.c_str(), mask.c_str()); } -#ifndef WIN32 -void CreatePidFile(const fs::path &path, pid_t pid) -{ - fsbridge::ofstream file{path}; - if (file) - { - tfm::format(file, "%d\n", pid); - } -} -#endif - /** * Ignores exceptions thrown by Boost's create_directories if the requested directory exists. * Specifically handles case where path p exists, but it wasn't possible for the user to diff --git a/src/util.h b/src/util.h index 3c043b7f1a..58524a5eb7 100644 --- a/src/util.h +++ b/src/util.h @@ -79,8 +79,6 @@ extern int GetDayOfYear(int64_t timestamp); extern bool fPrintToConsole; extern bool fRequestShutdown; extern std::atomic fShutdown; -extern bool fDaemon; -extern bool fServer; extern bool fCommandLine; extern bool fTestNet; extern bool fNoListen; @@ -100,9 +98,6 @@ bool TryCreateDirectories(const fs::path& p); std::string TimestampToHRDate(double dtm); -#ifndef WIN32 -void CreatePidFile(const fs::path &path, pid_t pid); -#endif bool DirIsWritable(const fs::path& directory); bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); bool TryCreateDirectories(const fs::path& p); diff --git a/src/util/syserror.cpp b/src/util/syserror.cpp new file mode 100644 index 0000000000..b48d920671 --- /dev/null +++ b/src/util/syserror.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include + +#include + +std::string SysErrorString(int err) +{ + char buf[1024]; + /* Too bad there are three incompatible implementations of the + * thread-safe strerror. */ + const char *s = nullptr; +#ifdef WIN32 + if (strerror_s(buf, sizeof(buf), err) == 0) s = buf; +#else +#ifdef STRERROR_R_CHAR_P /* GNU variant can return a pointer outside the passed buffer */ + s = strerror_r(err, buf, sizeof(buf)); +#else /* POSIX variant always returns message in buffer */ + if (strerror_r(err, buf, sizeof(buf)) == 0) s = buf; +#endif +#endif + if (s != nullptr) { + return strprintf("%s (%d)", s, err); + } else { + return strprintf("Unknown error (%d)", err); + } +} diff --git a/src/util/syserror.h b/src/util/syserror.h new file mode 100644 index 0000000000..1381d80822 --- /dev/null +++ b/src/util/syserror.h @@ -0,0 +1,16 @@ +// Copyright (c) 2010-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_SYSERROR_H +#define BITCOIN_UTIL_SYSERROR_H + +#include + +/** Return system error string from errno value. Use this instead of + * std::strerror, which is not thread-safe. For network errors use + * NetworkErrorString from sock.h instead. + */ +std::string SysErrorString(int err); + +#endif // BITCOIN_UTIL_SYSERROR_H diff --git a/src/util/tokenpipe.cpp b/src/util/tokenpipe.cpp new file mode 100644 index 0000000000..82c7328c77 --- /dev/null +++ b/src/util/tokenpipe.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2021-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. +#include + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#ifndef WIN32 + +#include +#include +#include +#include + +TokenPipeEnd TokenPipe::TakeReadEnd() +{ + TokenPipeEnd res(m_fds[0]); + m_fds[0] = -1; + return res; +} + +TokenPipeEnd TokenPipe::TakeWriteEnd() +{ + TokenPipeEnd res(m_fds[1]); + m_fds[1] = -1; + return res; +} + +TokenPipeEnd::TokenPipeEnd(int fd) : m_fd(fd) +{ +} + +TokenPipeEnd::~TokenPipeEnd() +{ + Close(); +} + +int TokenPipeEnd::TokenWrite(uint8_t token) +{ + while (true) { + ssize_t result = write(m_fd, &token, 1); + if (result < 0) { + // Failure. It's possible that the write was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return 0; + } + } +} + +int TokenPipeEnd::TokenRead() +{ + uint8_t token; + while (true) { + ssize_t result = read(m_fd, &token, 1); + if (result < 0) { + // Failure. Check if the read was interrupted by a signal, + // in that case retry. + if (errno != EINTR) { + return TS_ERR; + } + } else if (result == 0) { + return TS_EOS; + } else { // ==1 + return token; + } + } + return token; +} + +void TokenPipeEnd::Close() +{ + if (m_fd != -1) close(m_fd); + m_fd = -1; +} + +std::optional TokenPipe::Make() +{ + int fds[2] = {-1, -1}; +#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2 + if (pipe2(fds, O_CLOEXEC) != 0) { + return std::nullopt; + } +#else + if (pipe(fds) != 0) { + return std::nullopt; + } +#endif + return TokenPipe(fds); +} + +TokenPipe::~TokenPipe() +{ + Close(); +} + +void TokenPipe::Close() +{ + if (m_fds[0] != -1) close(m_fds[0]); + if (m_fds[1] != -1) close(m_fds[1]); + m_fds[0] = m_fds[1] = -1; +} + +#endif // WIN32 diff --git a/src/util/tokenpipe.h b/src/util/tokenpipe.h new file mode 100644 index 0000000000..3ce8c7b6aa --- /dev/null +++ b/src/util/tokenpipe.h @@ -0,0 +1,127 @@ +// Copyright (c) 2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_TOKENPIPE_H +#define BITCOIN_UTIL_TOKENPIPE_H + +#ifndef WIN32 + +#include +#include + +/** One end of a token pipe. */ +class TokenPipeEnd +{ +private: + int m_fd = -1; + +public: + TokenPipeEnd(int fd = -1); + ~TokenPipeEnd(); + + /** Return value constants for TokenWrite and TokenRead. */ + enum Status { + TS_ERR = -1, //!< I/O error + TS_EOS = -2, //!< Unexpected end of stream + }; + + /** Write token to endpoint. + * + * @returns 0 If successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenWrite(uint8_t token); + + /** Read token from endpoint. + * + * @returns >=0 Token value, if successful. + * <0 if error: + * TS_ERR If an error happened. + * TS_EOS If end of stream happened. + */ + int TokenRead(); + + /** Explicit close function. + */ + void Close(); + + /** Return whether endpoint is open. + */ + bool IsOpen() { return m_fd != -1; } + + // Move-only class. + TokenPipeEnd(TokenPipeEnd&& other) + { + m_fd = other.m_fd; + other.m_fd = -1; + } + TokenPipeEnd& operator=(TokenPipeEnd&& other) + { + Close(); + m_fd = other.m_fd; + other.m_fd = -1; + return *this; + } + TokenPipeEnd(const TokenPipeEnd&) = delete; + TokenPipeEnd& operator=(const TokenPipeEnd&) = delete; +}; + +/** An interprocess or interthread pipe for sending tokens (one-byte values) + * over. + */ +class TokenPipe +{ +private: + int m_fds[2] = {-1, -1}; + + TokenPipe(int fds[2]) : m_fds{fds[0], fds[1]} {} + +public: + ~TokenPipe(); + + /** Create a new pipe. + * @returns The created TokenPipe, or an empty std::nullopt in case of error. + */ + static std::optional Make(); + + /** Take the read end of this pipe. This can only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeReadEnd(); + + /** Take the write end of this pipe. This should only be called once, + * as the object will be moved out. + */ + TokenPipeEnd TakeWriteEnd(); + + /** Close and end of the pipe that hasn't been moved out. + */ + void Close(); + + // Move-only class. + TokenPipe(TokenPipe&& other) + { + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + } + TokenPipe& operator=(TokenPipe&& other) + { + Close(); + for (int i = 0; i < 2; ++i) { + m_fds[i] = other.m_fds[i]; + other.m_fds[i] = -1; + } + return *this; + } + TokenPipe(const TokenPipe&) = delete; + TokenPipe& operator=(const TokenPipe&) = delete; +}; + +#endif // WIN32 + +#endif // BITCOIN_UTIL_TOKENPIPE_H