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