fs_helpers.cpp
1 // Copyright (c) 2009-2010 Satoshi Nakamoto 2 // Copyright (c) 2009-2023 The Bitcoin Core developers 3 // Distributed under the MIT software license, see the accompanying 4 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 #include <util/fs_helpers.h> 7 8 #if defined(HAVE_CONFIG_H) 9 #include <config/bitcoin-config.h> 10 #endif 11 12 #include <logging.h> 13 #include <sync.h> 14 #include <util/fs.h> 15 #include <util/syserror.h> 16 17 #include <cerrno> 18 #include <fstream> 19 #include <map> 20 #include <memory> 21 #include <string> 22 #include <system_error> 23 #include <utility> 24 25 #ifndef WIN32 26 // for posix_fallocate, in configure.ac we check if it is present after this 27 #ifdef __linux__ 28 29 #ifdef _POSIX_C_SOURCE 30 #undef _POSIX_C_SOURCE 31 #endif 32 33 #define _POSIX_C_SOURCE 200112L 34 35 #endif // __linux__ 36 37 #include <fcntl.h> 38 #include <sys/resource.h> 39 #include <unistd.h> 40 #else 41 #include <io.h> /* For _get_osfhandle, _chsize */ 42 #include <shlobj.h> /* For SHGetSpecialFolderPathW */ 43 #endif // WIN32 44 45 /** Mutex to protect dir_locks. */ 46 static GlobalMutex cs_dir_locks; 47 /** A map that contains all the currently held directory locks. After 48 * successful locking, these will be held here until the global destructor 49 * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks 50 * is called. 51 */ 52 static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); 53 namespace util { 54 LockResult LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) 55 { 56 LOCK(cs_dir_locks); 57 fs::path pathLockFile = directory / lockfile_name; 58 59 // If a lock for this directory already exists in the map, don't try to re-lock it 60 if (dir_locks.count(fs::PathToString(pathLockFile))) { 61 return LockResult::Success; 62 } 63 64 // Create empty lock file if it doesn't exist. 65 if (auto created{fsbridge::fopen(pathLockFile, "a")}) { 66 std::fclose(created); 67 } else { 68 return LockResult::ErrorWrite; 69 } 70 auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); 71 if (!lock->TryLock()) { 72 LogError("Error while attempting to lock directory %s: %s\n", fs::PathToString(directory), lock->GetReason()); 73 return LockResult::ErrorLock; 74 } 75 if (!probe_only) { 76 // Lock successful and we're not just probing, put it into the map 77 dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); 78 } 79 return LockResult::Success; 80 } 81 } // namespace util 82 void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) 83 { 84 LOCK(cs_dir_locks); 85 dir_locks.erase(fs::PathToString(directory / lockfile_name)); 86 } 87 88 void ReleaseDirectoryLocks() 89 { 90 LOCK(cs_dir_locks); 91 dir_locks.clear(); 92 } 93 94 bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes) 95 { 96 constexpr uint64_t min_disk_space = 52428800; // 50 MiB 97 98 uint64_t free_bytes_available = fs::space(dir).available; 99 return free_bytes_available >= min_disk_space + additional_bytes; 100 } 101 102 std::streampos GetFileSize(const char* path, std::streamsize max) 103 { 104 std::ifstream file{path, std::ios::binary}; 105 file.ignore(max); 106 return file.gcount(); 107 } 108 109 bool FileCommit(FILE* file) 110 { 111 if (fflush(file) != 0) { // harmless if redundantly called 112 LogPrintf("fflush failed: %s\n", SysErrorString(errno)); 113 return false; 114 } 115 #ifdef WIN32 116 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); 117 if (FlushFileBuffers(hFile) == 0) { 118 LogPrintf("FlushFileBuffers failed: %s\n", Win32ErrorString(GetLastError())); 119 return false; 120 } 121 #elif defined(MAC_OSX) && defined(F_FULLFSYNC) 122 if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success 123 LogPrintf("fcntl F_FULLFSYNC failed: %s\n", SysErrorString(errno)); 124 return false; 125 } 126 #elif HAVE_FDATASYNC 127 if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync 128 LogPrintf("fdatasync failed: %s\n", SysErrorString(errno)); 129 return false; 130 } 131 #else 132 if (fsync(fileno(file)) != 0 && errno != EINVAL) { 133 LogPrintf("fsync failed: %s\n", SysErrorString(errno)); 134 return false; 135 } 136 #endif 137 return true; 138 } 139 140 void DirectoryCommit(const fs::path& dirname) 141 { 142 #ifndef WIN32 143 FILE* file = fsbridge::fopen(dirname, "r"); 144 if (file) { 145 fsync(fileno(file)); 146 fclose(file); 147 } 148 #endif 149 } 150 151 bool TruncateFile(FILE* file, unsigned int length) 152 { 153 #if defined(WIN32) 154 return _chsize(_fileno(file), length) == 0; 155 #else 156 return ftruncate(fileno(file), length) == 0; 157 #endif 158 } 159 160 /** 161 * this function tries to raise the file descriptor limit to the requested number. 162 * It returns the actual file descriptor limit (which may be more or less than nMinFD) 163 */ 164 int RaiseFileDescriptorLimit(int nMinFD) 165 { 166 #if defined(WIN32) 167 return 2048; 168 #else 169 struct rlimit limitFD; 170 if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { 171 if (limitFD.rlim_cur < (rlim_t)nMinFD) { 172 limitFD.rlim_cur = nMinFD; 173 if (limitFD.rlim_cur > limitFD.rlim_max) 174 limitFD.rlim_cur = limitFD.rlim_max; 175 setrlimit(RLIMIT_NOFILE, &limitFD); 176 getrlimit(RLIMIT_NOFILE, &limitFD); 177 } 178 return limitFD.rlim_cur; 179 } 180 return nMinFD; // getrlimit failed, assume it's fine 181 #endif 182 } 183 184 /** 185 * this function tries to make a particular range of a file allocated (corresponding to disk space) 186 * it is advisory, and the range specified in the arguments will never contain live data 187 */ 188 void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length) 189 { 190 #if defined(WIN32) 191 // Windows-specific version 192 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); 193 LARGE_INTEGER nFileSize; 194 int64_t nEndPos = (int64_t)offset + length; 195 nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; 196 nFileSize.u.HighPart = nEndPos >> 32; 197 SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); 198 SetEndOfFile(hFile); 199 #elif defined(MAC_OSX) 200 // OSX specific version 201 // NOTE: Contrary to other OS versions, the OSX version assumes that 202 // NOTE: offset is the size of the file. 203 fstore_t fst; 204 fst.fst_flags = F_ALLOCATECONTIG; 205 fst.fst_posmode = F_PEOFPOSMODE; 206 fst.fst_offset = 0; 207 fst.fst_length = length; // mac os fst_length takes the # of free bytes to allocate, not desired file size 208 fst.fst_bytesalloc = 0; 209 if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { 210 fst.fst_flags = F_ALLOCATEALL; 211 fcntl(fileno(file), F_PREALLOCATE, &fst); 212 } 213 ftruncate(fileno(file), static_cast<off_t>(offset) + length); 214 #else 215 #if defined(HAVE_POSIX_FALLOCATE) 216 // Version using posix_fallocate 217 off_t nEndPos = (off_t)offset + length; 218 if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return; 219 #endif 220 // Fallback version 221 // TODO: just write one byte per block 222 static const char buf[65536] = {}; 223 if (fseek(file, offset, SEEK_SET)) { 224 return; 225 } 226 while (length > 0) { 227 unsigned int now = 65536; 228 if (length < now) 229 now = length; 230 fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway 231 length -= now; 232 } 233 #endif 234 } 235 236 #ifdef WIN32 237 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) 238 { 239 WCHAR pszPath[MAX_PATH] = L""; 240 241 if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { 242 return fs::path(pszPath); 243 } 244 245 LogPrintf("SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); 246 return fs::path(""); 247 } 248 #endif 249 250 bool RenameOver(fs::path src, fs::path dest) 251 { 252 std::error_code error; 253 fs::rename(src, dest, error); 254 return !error; 255 } 256 257 /** 258 * Ignores exceptions thrown by create_directories if the requested directory exists. 259 * Specifically handles case where path p exists, but it wasn't possible for the user to 260 * write to the parent directory. 261 */ 262 bool TryCreateDirectories(const fs::path& p) 263 { 264 try { 265 return fs::create_directories(p); 266 } catch (const fs::filesystem_error&) { 267 if (!fs::exists(p) || !fs::is_directory(p)) 268 throw; 269 } 270 271 // create_directories didn't create the directory, it had to have existed already 272 return false; 273 }