diff options
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..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; +} |