diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -337,6 +337,9 @@ "-alertnotify=", _("Execute command when a relevant alert is received or we see a " "really long fork (%s in cmd is replaced by message)")); + strUsage += HelpMessageOpt( + "-blocksdir=", + _("Specify blocks directory (default: /blocks)")); strUsage += HelpMessageOpt("-blocknotify=", _("Execute command when the best block changes " "(%s in cmd is replaced by block hash)")); @@ -1005,7 +1008,7 @@ // ordered map keyed by block file index. LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for " "-reindex with -prune\n"); - fs::path blocksdir = GetDataDir() / "blocks"; + fs::path blocksdir = GetBlocksDir(); for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) { if (is_regular_file(*it) && @@ -1371,6 +1374,12 @@ // also see: InitParameterInteraction() + if (!fs::is_directory(GetBlocksDir(false))) { + return InitError( + strprintf(_("Specified blocks directory \"%s\" does not exist.\n"), + gArgs.GetArg("-blocksdir", "").c_str())); + } + // if using block pruning, then disallow txindex if (gArgs.GetArg("-prune", 0)) { if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { @@ -2243,7 +2252,14 @@ } // Step 10: import blocks - if (!CheckDiskSpace()) { + if (!CheckDiskSpace(/* additional_bytes */ 0, /* blocks_dir */ false)) { + InitError( + strprintf(_("Error: Disk space is low for %s"), GetDataDir())); + return false; + } + if (!CheckDiskSpace(/* additional_bytes */ 0, /* blocks_dir */ true)) { + InitError( + strprintf(_("Error: Disk space is low for %s"), GetBlocksDir())); return false; } diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -657,7 +657,8 @@ return EXIT_SUCCESS; } - /// 6. Determine availability of data directory and parse bitcoin.conf + /// 6. Determine availability of data and blocks directory and parse + /// bitcoin.conf /// - Do not call GetDataDir(true) before this step finishes. if (!fs::is_directory(GetDataDir(false))) { QMessageBox::critical( diff --git a/src/txdb.cpp b/src/txdb.cpp --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -154,8 +154,10 @@ } CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) - : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, - fWipe) {} + : CDBWrapper(gArgs.IsArgSet("-blocksdir") + ? GetDataDir() / "blocks" / "index" + : GetBlocksDir() / "index", + nCacheSize, fMemory, fWipe) {} bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { return Read(std::make_pair(DB_BLOCK_FILES, nFile), info); diff --git a/src/util.h b/src/util.h --- a/src/util.h +++ b/src/util.h @@ -84,6 +84,7 @@ bool TryCreateDirectories(const fs::path &p); fs::path GetDefaultDataDir(); +const fs::path &GetBlocksDir(bool fNetSpecific = true); const fs::path &GetDataDir(bool fNetSpecific = true); void ClearDatadirCache(); fs::path GetConfigFile(const std::string &confPath); diff --git a/src/util.cpp b/src/util.cpp --- a/src/util.cpp +++ b/src/util.cpp @@ -675,10 +675,39 @@ #endif } +static fs::path g_blocks_path_cached; +static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static CCriticalSection csPathCached; +const fs::path &GetBlocksDir(bool fNetSpecific) { + + LOCK(csPathCached); + + fs::path &path = + fNetSpecific ? g_blocks_path_cache_net_specific : g_blocks_path_cached; + + // This can be called during exceptions by LogPrintf(), so we cache the + // value so we don't have to do memory allocations after that. + if (!path.empty()) return path; + + if (gArgs.IsArgSet("-blocksdir")) { + path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); + if (!fs::is_directory(path)) { + path = ""; + return path; + } + } else { + path = GetDataDir(false); + } + if (fNetSpecific) path /= BaseParams().DataDir(); + + path /= "blocks"; + fs::create_directories(path); + return path; +} + const fs::path &GetDataDir(bool fNetSpecific) { LOCK(csPathCached); @@ -717,6 +746,8 @@ pathCached = fs::path(); pathCachedNetSpecific = fs::path(); + g_blocks_path_cached = fs::path(); + g_blocks_path_cache_net_specific = fs::path(); } fs::path GetConfigFile(const std::string &confPath) { diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -351,7 +351,7 @@ /** * Check whether enough disk space is available for an incoming block. */ -bool CheckDiskSpace(uint64_t nAdditionalBytes = 0); +bool CheckDiskSpace(uint64_t nAdditionalBytes = 0, bool blocks_dir = false); /** * Open a block file (blk?????.dat). diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2105,7 +2105,7 @@ // Write blocks and block index to disk. if (fDoFullFlush || fPeriodicWrite) { // Depend on nMinDiskSpace to ensure we can write block index - if (!CheckDiskSpace(0)) { + if (!CheckDiskSpace(0, true)) { return state.Error("out of disk space"); } @@ -3427,7 +3427,8 @@ fCheckForPruning = true; } - if (CheckDiskSpace(nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos)) { + if (CheckDiskSpace(nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos, + true)) { FILE *file = OpenBlockFile(pos); if (file) { LogPrintf( @@ -3468,7 +3469,7 @@ fCheckForPruning = true; } - if (CheckDiskSpace(nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos)) { + if (CheckDiskSpace(nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos, true)) { FILE *file = OpenUndoFile(pos); if (file) { LogPrintf("Pre-allocating up to position 0x%x in rev%05u.dat\n", @@ -4358,8 +4359,9 @@ nLastBlockWeCanPrune, count); } -bool CheckDiskSpace(uint64_t nAdditionalBytes) { - uint64_t nFreeBytesAvailable = fs::space(GetDataDir()).available; +bool CheckDiskSpace(uint64_t nAdditionalBytes, bool blocks_dir) { + uint64_t nFreeBytesAvailable = + fs::space(blocks_dir ? GetBlocksDir() : GetDataDir()).available; // Check for nMinDiskSpace bytes (currently 50MB) if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes) { @@ -4409,7 +4411,7 @@ } fs::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix) { - return GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, pos.nFile); + return GetBlocksDir() / strprintf("%s%05u.dat", prefix, pos.nFile); } CBlockIndex *CChainState::InsertBlockIndex(const uint256 &hash) { diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py new file mode 100755 --- /dev/null +++ b/test/functional/feature_blocksdir.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the blocksdir option. +""" + +from test_framework.test_framework import BitcoinTestFramework, initialize_datadir + +import shutil +import os + + +class BlocksdirTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def run_test(self): + self.stop_node(0) + node0path = os.path.join(self.options.tmpdir, "node0") + shutil.rmtree(node0path) + initialize_datadir(self.options.tmpdir, 0) + self.log.info("Starting with non exiting blocksdir ...") + self.assert_start_raises_init_error( + 0, ["-blocksdir="+self.options.tmpdir + "/blocksdir"], "Specified blocks director") + os.mkdir(self.options.tmpdir + "/blocksdir") + self.log.info("Starting with exiting blocksdir ...") + self.start_node(0, ["-blocksdir="+self.options.tmpdir + "/blocksdir"]) + self.log.info("mining blocks..") + self.nodes[0].generate(10) + assert(os.path.isfile(os.path.join(self.options.tmpdir, + "blocksdir", "regtest", "blocks", "blk00000.dat"))) + assert(os.path.isdir(os.path.join(self.options.tmpdir, + "node0", "regtest", "blocks", "index"))) + + +if __name__ == '__main__': + BlocksdirTest().main()