diff options
author | Ben Woosley <ben.woosley@gmail.com> | 2023-03-06 23:41:46 +0100 |
---|---|---|
committer | TheCharlatan <seb.kung@gmail.com> | 2023-03-23 12:52:00 +0100 |
commit | 18fb36367a28819bd5ab402344802796a1248979 (patch) | |
tree | f3b9230dd86440f68d1bad93040470e503f886f9 /src/util/fs_helpers.cpp | |
parent | 40e1c4d4024b8ad35f2511b2e10bf80c5531dfde (diff) | |
download | bitcoin-18fb36367a28819bd5ab402344802796a1248979.tar.xz |
refactor: Extract util/fs_helpers from util/system
This is an extraction of filesystem related functions from util/system
into their own utility file.
The background of this commit is an ongoing effort to decouple the
libbitcoinkernel library from the ArgsManager defined in system.h.
Moving these functions out of system.h allows including them from a
separate source file without including the ArgsManager definitions from
system.h.
Diffstat (limited to 'src/util/fs_helpers.cpp')
-rw-r--r-- | src/util/fs_helpers.cpp | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/src/util/fs_helpers.cpp b/src/util/fs_helpers.cpp new file mode 100644 index 0000000000..8ecaa07eb8 --- /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 <fs.h> +#include <logging.h> +#include <sync.h> +#include <tinyformat.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; +} |