mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-05-17 17:33:59 +02:00
wallet: handle non-writable db directories
1) For wallet load, this fixes a crash. We currently allow loading wallets located on non-writable directories. This is problematic because the node crashes on any subsequent write. E.g. generating a block is enough to trigger it. 2) For wallet creation, this improves the returned error msg. Before: creating a wallet would return a generic error: "SQLiteDatabase: Failed to open database: unable to open database file" After: creating a wallet returns: "SQLiteDatabase: Failed to open database in directory <dir_path>: directory is not writable"
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
#include <bitcoin-build-config.h> // IWYU pragma: keep
|
||||
|
||||
#include <util/fs_helpers.h>
|
||||
|
||||
#include <random.h>
|
||||
#include <sync.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/byte_units.h> // IWYU pragma: keep
|
||||
#include <util/fs.h>
|
||||
#include <util/log.h>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
@@ -306,6 +308,29 @@ std::optional<fs::perms> InterpretPermString(const std::string& s)
|
||||
}
|
||||
}
|
||||
|
||||
bool IsDirWritable(const fs::path& dir_path)
|
||||
{
|
||||
// Attempt to create a tmp file in the directory
|
||||
if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path)));
|
||||
FastRandomContext rng;
|
||||
const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64()));
|
||||
|
||||
const char* mode;
|
||||
#ifdef __MINGW64__
|
||||
mode = "w"; // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
|
||||
#else
|
||||
mode = "wx";
|
||||
#endif
|
||||
|
||||
if (const auto created{fsbridge::fopen(tmp, mode)}) {
|
||||
std::fclose(created);
|
||||
std::error_code ec;
|
||||
fs::remove(tmp, ec); // clean up, ignore errors
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
FSType GetFilesystemType(const fs::path& path)
|
||||
{
|
||||
|
||||
@@ -94,6 +94,14 @@ std::string PermsToSymbolicString(fs::perms p);
|
||||
*/
|
||||
std::optional<fs::perms> InterpretPermString(const std::string& s);
|
||||
|
||||
/** Check if a directory is writable by creating a temporary file on it.
|
||||
*
|
||||
* @param[in] dir_path Path of the directory to test
|
||||
* @return true if a temporary file could be created and removed, false otherwise.
|
||||
* @throw std::runtime_error if dir_path is not a directory.
|
||||
*/
|
||||
bool IsDirWritable(const fs::path& dir_path);
|
||||
|
||||
#ifdef WIN32
|
||||
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
|
||||
#endif
|
||||
|
||||
@@ -257,7 +257,11 @@ void SQLiteDatabase::Open(int additional_flags)
|
||||
if (m_db == nullptr) {
|
||||
if (!(flags & SQLITE_OPEN_MEMORY)) {
|
||||
TryCreateDirectories(m_dir_path);
|
||||
if (!IsDirWritable(m_dir_path)) {
|
||||
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database in directory '%s': directory is not writable", fs::PathToString(m_dir_path)));
|
||||
}
|
||||
}
|
||||
|
||||
int ret = sqlite3_open_v2(m_file_path.c_str(), &m_db, flags, nullptr);
|
||||
if (ret != SQLITE_OK) {
|
||||
throw std::runtime_error(strprintf("SQLiteDatabase: Failed to open database: %s\n", sqlite3_errstr(ret)));
|
||||
|
||||
Reference in New Issue
Block a user