diff options
Diffstat (limited to 'src/util')
-rw-r--r-- | src/util/asmap.cpp | 2 | ||||
-rw-r--r-- | src/util/asmap.h | 2 | ||||
-rw-r--r-- | src/util/fs.cpp | 138 | ||||
-rw-r--r-- | src/util/fs.h | 251 | ||||
-rw-r--r-- | src/util/fs_helpers.cpp | 295 | ||||
-rw-r--r-- | src/util/fs_helpers.h | 63 | ||||
-rw-r--r-- | src/util/getuniquepath.cpp | 2 | ||||
-rw-r--r-- | src/util/getuniquepath.h | 2 | ||||
-rw-r--r-- | src/util/readwritefile.cpp | 2 | ||||
-rw-r--r-- | src/util/readwritefile.h | 2 | ||||
-rw-r--r-- | src/util/settings.cpp | 2 | ||||
-rw-r--r-- | src/util/settings.h | 2 | ||||
-rw-r--r-- | src/util/system.cpp | 264 | ||||
-rw-r--r-- | src/util/system.h | 50 |
14 files changed, 759 insertions, 318 deletions
diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp index 4b548c5a4d..360573cbae 100644 --- a/src/util/asmap.cpp +++ b/src/util/asmap.cpp @@ -6,10 +6,10 @@ #include <clientversion.h> #include <crypto/common.h> -#include <fs.h> #include <logging.h> #include <serialize.h> #include <streams.h> +#include <util/fs.h> #include <algorithm> #include <cassert> diff --git a/src/util/asmap.h b/src/util/asmap.h index 844037f816..08a88f1b3c 100644 --- a/src/util/asmap.h +++ b/src/util/asmap.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_ASMAP_H #define BITCOIN_UTIL_ASMAP_H -#include <fs.h> +#include <util/fs.h> #include <cstdint> #include <vector> diff --git a/src/util/fs.cpp b/src/util/fs.cpp new file mode 100644 index 0000000000..e8fb72670f --- /dev/null +++ b/src/util/fs.cpp @@ -0,0 +1,138 @@ +// Copyright (c) 2017-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/fs.h> +#include <util/syserror.h> + +#ifndef WIN32 +#include <cstring> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/utsname.h> +#include <unistd.h> +#else +#include <codecvt> +#include <limits> +#include <windows.h> +#endif + +#include <cassert> +#include <string> + +namespace fsbridge { + +FILE *fopen(const fs::path& p, const char *mode) +{ +#ifndef WIN32 + return ::fopen(p.c_str(), mode); +#else + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t> utf8_cvt; + return ::_wfopen(p.wstring().c_str(), utf8_cvt.from_bytes(mode).c_str()); +#endif +} + +fs::path AbsPathJoin(const fs::path& base, const fs::path& path) +{ + assert(base.is_absolute()); + return path.empty() ? base : fs::path(base / path); +} + +#ifndef WIN32 + +static std::string GetErrorReason() +{ + return SysErrorString(errno); +} + +FileLock::FileLock(const fs::path& file) +{ + fd = open(file.c_str(), O_RDWR); + if (fd == -1) { + reason = GetErrorReason(); + } +} + +FileLock::~FileLock() +{ + if (fd != -1) { + close(fd); + } +} + +bool FileLock::TryLock() +{ + if (fd == -1) { + return false; + } + + struct flock lock; + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd, F_SETLK, &lock) == -1) { + reason = GetErrorReason(); + return false; + } + + return true; +} +#else + +static std::string GetErrorReason() { + wchar_t* err; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<WCHAR*>(&err), 0, nullptr); + std::wstring err_str(err); + LocalFree(err); + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(err_str); +} + +FileLock::FileLock(const fs::path& file) +{ + hFile = CreateFileW(file.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + reason = GetErrorReason(); + } +} + +FileLock::~FileLock() +{ + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + } +} + +bool FileLock::TryLock() +{ + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + _OVERLAPPED overlapped = {}; + if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits<DWORD>::max(), std::numeric_limits<DWORD>::max(), &overlapped)) { + reason = GetErrorReason(); + return false; + } + return true; +} +#endif + +std::string get_filesystem_error_message(const fs::filesystem_error& e) +{ +#ifndef WIN32 + return e.what(); +#else + // Convert from Multi Byte to utf-16 + std::string mb_string(e.what()); + int size = MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), nullptr, 0); + + std::wstring utf16_string(size, L'\0'); + MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), &*utf16_string.begin(), size); + // Convert from utf-16 to utf-8 + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>().to_bytes(utf16_string); +#endif +} + +} // fsbridge diff --git a/src/util/fs.h b/src/util/fs.h new file mode 100644 index 0000000000..886a30394e --- /dev/null +++ b/src/util/fs.h @@ -0,0 +1,251 @@ +// Copyright (c) 2017-2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_FS_H +#define BITCOIN_UTIL_FS_H + +#include <tinyformat.h> + +#include <cstdio> +#include <filesystem> +#include <functional> +#include <iomanip> +#include <ios> +#include <ostream> +#include <string> +#include <utility> + +/** Filesystem operations and types */ +namespace fs { + +using namespace std::filesystem; + +/** + * Path class wrapper to block calls to the fs::path(std::string) implicit + * constructor and the fs::path::string() method, which have unsafe and + * unpredictable behavior on Windows (see implementation note in + * \ref PathToString for details) + */ +class path : public std::filesystem::path +{ +public: + using std::filesystem::path::path; + + // Allow path objects arguments for compatibility. + path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {} + path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; } + path& operator/=(std::filesystem::path path) { std::filesystem::path::operator/=(path); return *this; } + + // Allow literal string arguments, which are safe as long as the literals are ASCII. + path(const char* c) : std::filesystem::path(c) {} + path& operator=(const char* c) { std::filesystem::path::operator=(c); return *this; } + path& operator/=(const char* c) { std::filesystem::path::operator/=(c); return *this; } + path& append(const char* c) { std::filesystem::path::append(c); return *this; } + + // Disallow std::string arguments to avoid locale-dependent decoding on windows. + path(std::string) = delete; + path& operator=(std::string) = delete; + path& operator/=(std::string) = delete; + path& append(std::string) = delete; + + // Disallow std::string conversion method to avoid locale-dependent encoding on windows. + std::string string() const = delete; + + std::string u8string() const + { + const auto& utf8_str{std::filesystem::path::u8string()}; + // utf8_str might either be std::string (C++17) or std::u8string + // (C++20). Convert both to std::string. This method can be removed + // after switching to C++20. + return std::string{utf8_str.begin(), utf8_str.end()}; + } + + // Required for path overloads in <fstream>. + // See https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=96e0367ead5d8dcac3bec2865582e76e2fbab190 + path& make_preferred() { std::filesystem::path::make_preferred(); return *this; } + path filename() const { return std::filesystem::path::filename(); } +}; + +static inline path u8path(const std::string& utf8_str) +{ +#if __cplusplus < 202002L + return std::filesystem::u8path(utf8_str); +#else + return std::filesystem::path(std::u8string{utf8_str.begin(), utf8_str.end()}); +#endif +} + +// Disallow implicit std::string conversion for absolute to avoid +// locale-dependent encoding on windows. +static inline path absolute(const path& p) +{ + return std::filesystem::absolute(p); +} + +// Disallow implicit std::string conversion for exists to avoid +// locale-dependent encoding on windows. +static inline bool exists(const path& p) +{ + return std::filesystem::exists(p); +} + +// Allow explicit quoted stream I/O. +static inline auto quoted(const std::string& s) +{ + return std::quoted(s, '"', '&'); +} + +// Allow safe path append operations. +static inline path operator/(path p1, path p2) +{ + p1 /= std::move(p2); + return p1; +} +static inline path operator/(path p1, const char* p2) +{ + p1 /= p2; + return p1; +} +static inline path operator+(path p1, const char* p2) +{ + p1 += p2; + return p1; +} +static inline path operator+(path p1, path::value_type p2) +{ + p1 += p2; + return p1; +} + +// Disallow unsafe path append operations. +template<typename T> static inline path operator/(path p1, T p2) = delete; +template<typename T> static inline path operator+(path p1, T p2) = delete; + +// Disallow implicit std::string conversion for copy_file +// to avoid locale-dependent encoding on Windows. +static inline bool copy_file(const path& from, const path& to, copy_options options) +{ + return std::filesystem::copy_file(from, to, options); +} + +/** + * Convert path object to a byte string. On POSIX, paths natively are byte + * strings, so this is trivial. On Windows, paths natively are Unicode, so an + * encoding step is necessary. The inverse of \ref PathToString is \ref + * PathFromString. The strings returned and parsed by these functions can be + * used to call POSIX APIs, and for roundtrip conversion, logging, and + * debugging. + * + * Because \ref PathToString and \ref PathFromString functions don't specify an + * encoding, they are meant to be used internally, not externally. They are not + * appropriate to use in applications requiring UTF-8, where + * fs::path::u8string() and fs::u8path() methods should be used instead. Other + * applications could require still different encodings. For example, JSON, XML, + * or URI applications might prefer to use higher-level escapes (\uXXXX or + * &XXXX; or %XX) instead of multibyte encoding. Rust, Python, Java applications + * may require encoding paths with their respective UTF-8 derivatives WTF-8, + * PEP-383, and CESU-8 (see https://en.wikipedia.org/wiki/UTF-8#Derivatives). + */ +static inline std::string PathToString(const path& path) +{ + // Implementation note: On Windows, the std::filesystem::path(string) + // constructor and std::filesystem::path::string() method are not safe to + // use here, because these methods encode the path using C++'s narrow + // multibyte encoding, which on Windows corresponds to the current "code + // page", which is unpredictable and typically not able to represent all + // valid paths. So fs::path::u8string() and + // fs::u8path() functions are used instead on Windows. On + // POSIX, u8string/u8path functions are not safe to use because paths are + // not always valid UTF-8, so plain string methods which do not transform + // the path there are used. +#ifdef WIN32 + return path.u8string(); +#else + static_assert(std::is_same<path::string_type, std::string>::value, "PathToString not implemented on this platform"); + return path.std::filesystem::path::string(); +#endif +} + +/** + * Convert byte string to path object. Inverse of \ref PathToString. + */ +static inline path PathFromString(const std::string& string) +{ +#ifdef WIN32 + return u8path(string); +#else + return std::filesystem::path(string); +#endif +} + +/** + * Create directory (and if necessary its parents), unless the leaf directory + * already exists or is a symlink to an existing directory. + * This is a temporary workaround for an issue in libstdc++ that has been fixed + * upstream [PR101510]. + */ +static inline bool create_directories(const std::filesystem::path& p) +{ + if (std::filesystem::is_symlink(p) && std::filesystem::is_directory(p)) { + return false; + } + return std::filesystem::create_directories(p); +} + +/** + * This variant is not used. Delete it to prevent it from accidentally working + * around the workaround. If it is needed, add a workaround in the same pattern + * as above. + */ +bool create_directories(const std::filesystem::path& p, std::error_code& ec) = delete; + +} // namespace fs + +/** Bridge operations to C stdio */ +namespace fsbridge { + using FopenFn = std::function<FILE*(const fs::path&, const char*)>; + FILE *fopen(const fs::path& p, const char *mode); + + /** + * Helper function for joining two paths + * + * @param[in] base Base path + * @param[in] path Path to combine with base + * @returns path unchanged if it is an absolute path, otherwise returns base joined with path. Returns base unchanged if path is empty. + * @pre Base path must be absolute + * @post Returned path will always be absolute + */ + fs::path AbsPathJoin(const fs::path& base, const fs::path& path); + + class FileLock + { + public: + FileLock() = delete; + FileLock(const FileLock&) = delete; + FileLock(FileLock&&) = delete; + explicit FileLock(const fs::path& file); + ~FileLock(); + bool TryLock(); + std::string GetReason() { return reason; } + + private: + std::string reason; +#ifndef WIN32 + int fd = -1; +#else + void* hFile = (void*)-1; // INVALID_HANDLE_VALUE +#endif + }; + + std::string get_filesystem_error_message(const fs::filesystem_error& e); +}; + +// Disallow path operator<< formatting in tinyformat to avoid locale-dependent +// encoding on windows. +namespace tinyformat { +template<> inline void formatValue(std::ostream&, const char*, const char*, int, const std::filesystem::path&) = delete; +template<> inline void formatValue(std::ostream&, const char*, const char*, int, const fs::path&) = delete; +} // namespace tinyformat + +#endif // BITCOIN_UTIL_FS_H diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp new file mode 100644 index 0000000000..d05cb8a63d --- /dev/null +++ b/src/util/fs_helpers.cpp @@ -0,0 +1,295 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/fs_helpers.h> + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <logging.h> +#include <sync.h> +#include <tinyformat.h> +#include <util/fs.h> +#include <util/getuniquepath.h> + +#include <cerrno> +#include <filesystem> +#include <fstream> +#include <map> +#include <memory> +#include <string> +#include <system_error> +#include <utility> + +#ifndef WIN32 +// for posix_fallocate, in configure.ac we check if it is present after this +#ifdef __linux__ + +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#define _POSIX_C_SOURCE 200112L + +#endif // __linux__ + +#include <fcntl.h> +#include <sys/resource.h> +#include <unistd.h> +#else +#include <io.h> /* For _get_osfhandle, _chsize */ +#include <shlobj.h> /* For SHGetSpecialFolderPathW */ +#endif // WIN32 + +/** Mutex to protect dir_locks. */ +static GlobalMutex cs_dir_locks; +/** A map that contains all the currently held directory locks. After + * successful locking, these will be held here until the global destructor + * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks + * is called. + */ +static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); + +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) +{ + LOCK(cs_dir_locks); + fs::path pathLockFile = directory / lockfile_name; + + // If a lock for this directory already exists in the map, don't try to re-lock it + if (dir_locks.count(fs::PathToString(pathLockFile))) { + return true; + } + + // Create empty lock file if it doesn't exist. + FILE* file = fsbridge::fopen(pathLockFile, "a"); + if (file) fclose(file); + auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); + if (!lock->TryLock()) { + return error("Error while attempting to lock directory %s: %s", fs::PathToString(directory), lock->GetReason()); + } + if (!probe_only) { + // Lock successful and we're not just probing, put it into the map + dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); + } + return true; +} + +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) +{ + LOCK(cs_dir_locks); + dir_locks.erase(fs::PathToString(directory / lockfile_name)); +} + +void ReleaseDirectoryLocks() +{ + LOCK(cs_dir_locks); + dir_locks.clear(); +} + +bool DirIsWritable(const fs::path& directory) +{ + fs::path tmpFile = GetUniquePath(directory); + + FILE* file = fsbridge::fopen(tmpFile, "a"); + if (!file) return false; + + fclose(file); + remove(tmpFile); + + return true; +} + +bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes) +{ + constexpr uint64_t min_disk_space = 52428800; // 50 MiB + + uint64_t free_bytes_available = fs::space(dir).available; + return free_bytes_available >= min_disk_space + additional_bytes; +} + +std::streampos GetFileSize(const char* path, std::streamsize max) +{ + std::ifstream file{path, std::ios::binary}; + file.ignore(max); + return file.gcount(); +} + +bool FileCommit(FILE* file) +{ + if (fflush(file) != 0) { // harmless if redundantly called + LogPrintf("%s: fflush failed: %d\n", __func__, errno); + return false; + } +#ifdef WIN32 + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); + if (FlushFileBuffers(hFile) == 0) { + LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); + return false; + } +#elif defined(MAC_OSX) && defined(F_FULLFSYNC) + if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success + LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); + return false; + } +#elif HAVE_FDATASYNC + if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync + LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); + return false; + } +#else + if (fsync(fileno(file)) != 0 && errno != EINVAL) { + LogPrintf("%s: fsync failed: %d\n", __func__, errno); + return false; + } +#endif + return true; +} + +void DirectoryCommit(const fs::path& dirname) +{ +#ifndef WIN32 + FILE* file = fsbridge::fopen(dirname, "r"); + if (file) { + fsync(fileno(file)); + fclose(file); + } +#endif +} + +bool TruncateFile(FILE* file, unsigned int length) +{ +#if defined(WIN32) + return _chsize(_fileno(file), length) == 0; +#else + return ftruncate(fileno(file), length) == 0; +#endif +} + +/** + * this function tries to raise the file descriptor limit to the requested number. + * It returns the actual file descriptor limit (which may be more or less than nMinFD) + */ +int RaiseFileDescriptorLimit(int nMinFD) +{ +#if defined(WIN32) + return 2048; +#else + struct rlimit limitFD; + if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { + if (limitFD.rlim_cur < (rlim_t)nMinFD) { + limitFD.rlim_cur = nMinFD; + if (limitFD.rlim_cur > limitFD.rlim_max) + limitFD.rlim_cur = limitFD.rlim_max; + setrlimit(RLIMIT_NOFILE, &limitFD); + getrlimit(RLIMIT_NOFILE, &limitFD); + } + return limitFD.rlim_cur; + } + return nMinFD; // getrlimit failed, assume it's fine +#endif +} + +/** + * this function tries to make a particular range of a file allocated (corresponding to disk space) + * it is advisory, and the range specified in the arguments will never contain live data + */ +void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length) +{ +#if defined(WIN32) + // Windows-specific version + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); + LARGE_INTEGER nFileSize; + int64_t nEndPos = (int64_t)offset + length; + nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; + nFileSize.u.HighPart = nEndPos >> 32; + SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); + SetEndOfFile(hFile); +#elif defined(MAC_OSX) + // OSX specific version + // NOTE: Contrary to other OS versions, the OSX version assumes that + // NOTE: offset is the size of the file. + fstore_t fst; + fst.fst_flags = F_ALLOCATECONTIG; + fst.fst_posmode = F_PEOFPOSMODE; + fst.fst_offset = 0; + fst.fst_length = length; // mac os fst_length takes the # of free bytes to allocate, not desired file size + fst.fst_bytesalloc = 0; + if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { + fst.fst_flags = F_ALLOCATEALL; + fcntl(fileno(file), F_PREALLOCATE, &fst); + } + ftruncate(fileno(file), static_cast<off_t>(offset) + length); +#else +#if defined(HAVE_POSIX_FALLOCATE) + // Version using posix_fallocate + off_t nEndPos = (off_t)offset + length; + if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return; +#endif + // Fallback version + // TODO: just write one byte per block + static const char buf[65536] = {}; + if (fseek(file, offset, SEEK_SET)) { + return; + } + while (length > 0) { + unsigned int now = 65536; + if (length < now) + now = length; + fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway + length -= now; + } +#endif +} + +#ifdef WIN32 +fs::path GetSpecialFolderPath(int nFolder, bool fCreate) +{ + WCHAR pszPath[MAX_PATH] = L""; + + if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { + return fs::path(pszPath); + } + + LogPrintf("SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); + return fs::path(""); +} +#endif + +bool RenameOver(fs::path src, fs::path dest) +{ +#ifdef __MINGW64__ + // This is a workaround for a bug in libstdc++ which + // implements std::filesystem::rename with _wrename function. + // This bug has been fixed in upstream: + // - GCC 10.3: 8dd1c1085587c9f8a21bb5e588dfe1e8cdbba79e + // - GCC 11.1: 1dfd95f0a0ca1d9e6cbc00e6cbfd1fa20a98f312 + // For more details see the commits mentioned above. + return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(), + MOVEFILE_REPLACE_EXISTING) != 0; +#else + std::error_code error; + fs::rename(src, dest, error); + return !error; +#endif +} + +/** + * Ignores exceptions thrown by create_directories if the requested directory exists. + * Specifically handles case where path p exists, but it wasn't possible for the user to + * write to the parent directory. + */ +bool TryCreateDirectories(const fs::path& p) +{ + try { + return fs::create_directories(p); + } catch (const fs::filesystem_error&) { + if (!fs::exists(p) || !fs::is_directory(p)) + throw; + } + + // create_directories didn't create the directory, it had to have existed already + return false; +} diff --git a/src/util/fs_helpers.h b/src/util/fs_helpers.h new file mode 100644 index 0000000000..e7db01a89b --- /dev/null +++ b/src/util/fs_helpers.h @@ -0,0 +1,63 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_FS_HELPERS_H +#define BITCOIN_UTIL_FS_HELPERS_H + +#include <util/fs.h> + +#include <cstdint> +#include <cstdio> +#include <iosfwd> +#include <limits> + +/** + * Ensure file contents are fully committed to disk, using a platform-specific + * feature analogous to fsync(). + */ +bool FileCommit(FILE* file); + +/** + * Sync directory contents. This is required on some environments to ensure that + * newly created files are committed to disk. + */ +void DirectoryCommit(const fs::path& dirname); + +bool TruncateFile(FILE* file, unsigned int length); +int RaiseFileDescriptorLimit(int nMinFD); +void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length); + +/** + * Rename src to dest. + * @return true if the rename was successful. + */ +[[nodiscard]] bool RenameOver(fs::path src, fs::path dest); + +bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only = false); +void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name); +bool DirIsWritable(const fs::path& directory); +bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); + +/** Get the size of a file by scanning it. + * + * @param[in] path The file path + * @param[in] max Stop seeking beyond this limit + * @return The file size or max + */ +std::streampos GetFileSize(const char* path, std::streamsize max = std::numeric_limits<std::streamsize>::max()); + +/** Release all directory locks. This is used for unit testing only, at runtime + * the global destructor will take care of the locks. + */ +void ReleaseDirectoryLocks(); + +bool TryCreateDirectories(const fs::path& p); +fs::path GetDefaultDataDir(); + +#ifdef WIN32 +fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); +#endif + +#endif // BITCOIN_UTIL_FS_HELPERS_H diff --git a/src/util/getuniquepath.cpp b/src/util/getuniquepath.cpp index 42c5dee0ed..105b4d52d2 100644 --- a/src/util/getuniquepath.cpp +++ b/src/util/getuniquepath.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <random.h> -#include <fs.h> +#include <util/fs.h> #include <util/strencodings.h> fs::path GetUniquePath(const fs::path& base) diff --git a/src/util/getuniquepath.h b/src/util/getuniquepath.h index e0c6147876..1563652300 100644 --- a/src/util/getuniquepath.h +++ b/src/util/getuniquepath.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_GETUNIQUEPATH_H #define BITCOIN_UTIL_GETUNIQUEPATH_H -#include <fs.h> +#include <util/fs.h> /** * Helper function for getting a unique path diff --git a/src/util/readwritefile.cpp b/src/util/readwritefile.cpp index cc555adf45..773d7ad1cc 100644 --- a/src/util/readwritefile.cpp +++ b/src/util/readwritefile.cpp @@ -5,7 +5,7 @@ #include <util/readwritefile.h> -#include <fs.h> +#include <util/fs.h> #include <algorithm> #include <cstdio> diff --git a/src/util/readwritefile.h b/src/util/readwritefile.h index 73437baf1b..6ddfcb4f35 100644 --- a/src/util/readwritefile.h +++ b/src/util/readwritefile.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_READWRITEFILE_H #define BITCOIN_UTIL_READWRITEFILE_H -#include <fs.h> +#include <util/fs.h> #include <limits> #include <string> diff --git a/src/util/settings.cpp b/src/util/settings.cpp index a2e30098dc..bb257c0584 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <fs.h> +#include <util/fs.h> #include <util/settings.h> #include <tinyformat.h> diff --git a/src/util/settings.h b/src/util/settings.h index b0d8acb711..27e87a4751 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_UTIL_SETTINGS_H #define BITCOIN_UTIL_SETTINGS_H -#include <fs.h> +#include <util/fs.h> #include <map> #include <string> diff --git a/src/util/system.cpp b/src/util/system.cpp index 98e89f82e7..630eff99e0 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -6,9 +6,10 @@ #include <util/system.h> #include <chainparamsbase.h> -#include <fs.h> #include <sync.h> #include <util/check.h> +#include <util/fs.h> +#include <util/fs_helpers.h> #include <util/getuniquepath.h> #include <util/strencodings.h> #include <util/string.h> @@ -22,17 +23,6 @@ #endif #ifndef WIN32 -// for posix_fallocate, in configure.ac we check if it is present after this -#ifdef __linux__ - -#ifdef _POSIX_C_SOURCE -#undef _POSIX_C_SOURCE -#endif - -#define _POSIX_C_SOURCE 200112L - -#endif // __linux__ - #include <algorithm> #include <cassert> #include <fcntl.h> @@ -44,7 +34,6 @@ #include <codecvt> -#include <io.h> /* for _commit */ #include <shellapi.h> #include <shlobj.h> #endif @@ -72,78 +61,6 @@ const char * const BITCOIN_SETTINGS_FILENAME = "settings.json"; ArgsManager gArgs; -/** Mutex to protect dir_locks. */ -static GlobalMutex cs_dir_locks; -/** A map that contains all the currently held directory locks. After - * successful locking, these will be held here until the global destructor - * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks - * is called. - */ -static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); - -bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) -{ - LOCK(cs_dir_locks); - fs::path pathLockFile = directory / lockfile_name; - - // If a lock for this directory already exists in the map, don't try to re-lock it - if (dir_locks.count(fs::PathToString(pathLockFile))) { - return true; - } - - // Create empty lock file if it doesn't exist. - FILE* file = fsbridge::fopen(pathLockFile, "a"); - if (file) fclose(file); - auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); - if (!lock->TryLock()) { - return error("Error while attempting to lock directory %s: %s", fs::PathToString(directory), lock->GetReason()); - } - if (!probe_only) { - // Lock successful and we're not just probing, put it into the map - dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); - } - return true; -} - -void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) -{ - LOCK(cs_dir_locks); - dir_locks.erase(fs::PathToString(directory / lockfile_name)); -} - -void ReleaseDirectoryLocks() -{ - LOCK(cs_dir_locks); - dir_locks.clear(); -} - -bool DirIsWritable(const fs::path& directory) -{ - fs::path tmpFile = GetUniquePath(directory); - - FILE* file = fsbridge::fopen(tmpFile, "a"); - if (!file) return false; - - fclose(file); - remove(tmpFile); - - return true; -} - -bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes) -{ - constexpr uint64_t min_disk_space = 52428800; // 50 MiB - - uint64_t free_bytes_available = fs::space(dir).available; - return free_bytes_available >= min_disk_space + additional_bytes; -} - -std::streampos GetFileSize(const char* path, std::streamsize max) { - std::ifstream file{path, std::ios::binary}; - file.ignore(max); - return file.gcount(); -} - /** * Interpret a string argument as a boolean. * @@ -1091,182 +1008,6 @@ void ArgsManager::LogArgs() const logArgsPrefix("Command-line arg:", "", m_settings.command_line_options); } -bool RenameOver(fs::path src, fs::path dest) -{ -#ifdef __MINGW64__ - // This is a workaround for a bug in libstdc++ which - // implements std::filesystem::rename with _wrename function. - // This bug has been fixed in upstream: - // - GCC 10.3: 8dd1c1085587c9f8a21bb5e588dfe1e8cdbba79e - // - GCC 11.1: 1dfd95f0a0ca1d9e6cbc00e6cbfd1fa20a98f312 - // For more details see the commits mentioned above. - return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(), - MOVEFILE_REPLACE_EXISTING) != 0; -#else - std::error_code error; - fs::rename(src, dest, error); - return !error; -#endif -} - -/** - * Ignores exceptions thrown by create_directories if the requested directory exists. - * Specifically handles case where path p exists, but it wasn't possible for the user to - * write to the parent directory. - */ -bool TryCreateDirectories(const fs::path& p) -{ - try - { - return fs::create_directories(p); - } catch (const fs::filesystem_error&) { - if (!fs::exists(p) || !fs::is_directory(p)) - throw; - } - - // create_directories didn't create the directory, it had to have existed already - return false; -} - -bool FileCommit(FILE *file) -{ - if (fflush(file) != 0) { // harmless if redundantly called - LogPrintf("%s: fflush failed: %d\n", __func__, errno); - return false; - } -#ifdef WIN32 - HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); - if (FlushFileBuffers(hFile) == 0) { - LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); - return false; - } -#elif defined(MAC_OSX) && defined(F_FULLFSYNC) - if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success - LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); - return false; - } -#elif HAVE_FDATASYNC - if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync - LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); - return false; - } -#else - if (fsync(fileno(file)) != 0 && errno != EINVAL) { - LogPrintf("%s: fsync failed: %d\n", __func__, errno); - return false; - } -#endif - return true; -} - -void DirectoryCommit(const fs::path &dirname) -{ -#ifndef WIN32 - FILE* file = fsbridge::fopen(dirname, "r"); - if (file) { - fsync(fileno(file)); - fclose(file); - } -#endif -} - -bool TruncateFile(FILE *file, unsigned int length) { -#if defined(WIN32) - return _chsize(_fileno(file), length) == 0; -#else - return ftruncate(fileno(file), length) == 0; -#endif -} - -/** - * this function tries to raise the file descriptor limit to the requested number. - * It returns the actual file descriptor limit (which may be more or less than nMinFD) - */ -int RaiseFileDescriptorLimit(int nMinFD) { -#if defined(WIN32) - return 2048; -#else - struct rlimit limitFD; - if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { - if (limitFD.rlim_cur < (rlim_t)nMinFD) { - limitFD.rlim_cur = nMinFD; - if (limitFD.rlim_cur > limitFD.rlim_max) - limitFD.rlim_cur = limitFD.rlim_max; - setrlimit(RLIMIT_NOFILE, &limitFD); - getrlimit(RLIMIT_NOFILE, &limitFD); - } - return limitFD.rlim_cur; - } - return nMinFD; // getrlimit failed, assume it's fine -#endif -} - -/** - * this function tries to make a particular range of a file allocated (corresponding to disk space) - * it is advisory, and the range specified in the arguments will never contain live data - */ -void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { -#if defined(WIN32) - // Windows-specific version - HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); - LARGE_INTEGER nFileSize; - int64_t nEndPos = (int64_t)offset + length; - nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; - nFileSize.u.HighPart = nEndPos >> 32; - SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); - SetEndOfFile(hFile); -#elif defined(MAC_OSX) - // OSX specific version - // NOTE: Contrary to other OS versions, the OSX version assumes that - // NOTE: offset is the size of the file. - fstore_t fst; - fst.fst_flags = F_ALLOCATECONTIG; - fst.fst_posmode = F_PEOFPOSMODE; - fst.fst_offset = 0; - fst.fst_length = length; // mac os fst_length takes the # of free bytes to allocate, not desired file size - fst.fst_bytesalloc = 0; - if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { - fst.fst_flags = F_ALLOCATEALL; - fcntl(fileno(file), F_PREALLOCATE, &fst); - } - ftruncate(fileno(file), static_cast<off_t>(offset) + length); -#else - #if defined(HAVE_POSIX_FALLOCATE) - // Version using posix_fallocate - off_t nEndPos = (off_t)offset + length; - if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return; - #endif - // Fallback version - // TODO: just write one byte per block - static const char buf[65536] = {}; - if (fseek(file, offset, SEEK_SET)) { - return; - } - while (length > 0) { - unsigned int now = 65536; - if (length < now) - now = length; - fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway - length -= now; - } -#endif -} - -#ifdef WIN32 -fs::path GetSpecialFolderPath(int nFolder, bool fCreate) -{ - WCHAR pszPath[MAX_PATH] = L""; - - if(SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) - { - return fs::path(pszPath); - } - - LogPrintf("SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); - return fs::path(""); -} -#endif - #ifndef WIN32 std::string ShellEscape(const std::string& arg) { @@ -1290,7 +1031,6 @@ void runCommand(const std::string& strCommand) } #endif - void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX diff --git a/src/util/system.h b/src/util/system.h index 7292262bea..ff3155b498 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -14,11 +14,11 @@ #include <config/bitcoin-config.h> #endif -#include <compat/compat.h> #include <compat/assumptions.h> -#include <fs.h> +#include <compat/compat.h> #include <logging.h> #include <sync.h> +#include <util/fs.h> #include <util/settings.h> #include <util/time.h> @@ -42,55 +42,9 @@ extern const char * const BITCOIN_SETTINGS_FILENAME; void SetupEnvironment(); bool SetupNetworking(); - -/** - * Ensure file contents are fully committed to disk, using a platform-specific - * feature analogous to fsync(). - */ -bool FileCommit(FILE *file); - -/** - * Sync directory contents. This is required on some environments to ensure that - * newly created files are committed to disk. - */ -void DirectoryCommit(const fs::path &dirname); - -bool TruncateFile(FILE *file, unsigned int length); -int RaiseFileDescriptorLimit(int nMinFD); -void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); - -/** - * Rename src to dest. - * @return true if the rename was successful. - */ -[[nodiscard]] bool RenameOver(fs::path src, fs::path dest); - -bool LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only=false); -void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name); -bool DirIsWritable(const fs::path& directory); -bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes = 0); - -/** Get the size of a file by scanning it. - * - * @param[in] path The file path - * @param[in] max Stop seeking beyond this limit - * @return The file size or max - */ -std::streampos GetFileSize(const char* path, std::streamsize max = std::numeric_limits<std::streamsize>::max()); - -/** Release all directory locks. This is used for unit testing only, at runtime - * the global destructor will take care of the locks. - */ -void ReleaseDirectoryLocks(); - -bool TryCreateDirectories(const fs::path& p); -fs::path GetDefaultDataDir(); // Return true if -datadir option points to a valid directory or is not specified. bool CheckDataDirOption(const ArgsManager& args); fs::path GetConfigFile(const ArgsManager& args, const fs::path& configuration_file_path); -#ifdef WIN32 -fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); -#endif #ifndef WIN32 std::string ShellEscape(const std::string& arg); #endif |