aboutsummaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/asmap.cpp2
-rw-r--r--src/util/asmap.h2
-rw-r--r--src/util/fs.cpp138
-rw-r--r--src/util/fs.h251
-rw-r--r--src/util/fs_helpers.cpp295
-rw-r--r--src/util/fs_helpers.h63
-rw-r--r--src/util/getuniquepath.cpp2
-rw-r--r--src/util/getuniquepath.h2
-rw-r--r--src/util/readwritefile.cpp2
-rw-r--r--src/util/readwritefile.h2
-rw-r--r--src/util/settings.cpp2
-rw-r--r--src/util/settings.h2
-rw-r--r--src/util/system.cpp264
-rw-r--r--src/util/system.h50
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