diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp index a41bf65ad86..097fbef9af3 100644 --- a/src/util/fs_helpers.cpp +++ b/src/util/fs_helpers.cpp @@ -6,8 +6,9 @@ #include // IWYU pragma: keep #include - +#include #include +#include #include // IWYU pragma: keep #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -306,6 +308,29 @@ std::optional 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) { diff --git a/src/util/fs_helpers.h b/src/util/fs_helpers.h index face17fd8b5..f4d406f75f0 100644 --- a/src/util/fs_helpers.h +++ b/src/util/fs_helpers.h @@ -94,6 +94,14 @@ std::string PermsToSymbolicString(fs::perms p); */ std::optional 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 diff --git a/src/wallet/sqlite.cpp b/src/wallet/sqlite.cpp index 3d6583bb037..17414521d0b 100644 --- a/src/wallet/sqlite.cpp +++ b/src/wallet/sqlite.cpp @@ -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)));