fs_helpers.cpp raw
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-present 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 <bitcoin-build-config.h> // IWYU pragma: keep
7
8 #include <util/fs_helpers.h>
9 #include <random.h>
10 #include <sync.h>
11 #include <tinyformat.h>
12 #include <util/byte_units.h> // IWYU pragma: keep
13 #include <util/check.h>
14 #include <util/fs.h>
15 #include <util/log.h>
16 #include <util/syserror.h>
17
18 #include <cerrno>
19 #include <fstream>
20 #include <limits>
21 #include <map>
22 #include <memory>
23 #include <optional>
24 #include <stdexcept>
25 #include <string>
26 #include <system_error>
27 #include <utility>
28
29 #ifndef WIN32
30 #include <fcntl.h>
31 #include <sys/resource.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34 #else
35 #include <io.h>
36 #include <shlobj.h>
37 #endif // WIN32
38
39 #ifdef __APPLE__
40 #include <sys/mount.h>
41 #include <sys/param.h>
42 #endif
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.contains(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{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 LogError("fflush failed: %s", SysErrorString(errno));
112 return false;
113 }
114 #ifdef WIN32
115 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
116 if (FlushFileBuffers(hFile) == 0) {
117 LogError("FlushFileBuffers failed: %s", 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 LogError("fcntl F_FULLFSYNC failed: %s", 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 LogError("fdatasync failed: %s", SysErrorString(errno));
128 return false;
129 }
130 #else
131 if (fsync(fileno(file)) != 0 && errno != EINVAL) {
132 LogError("fsync failed: %s", 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 int RaiseFileDescriptorLimit(int min_fd)
160 {
161 Assert(min_fd >= 0);
162 #if defined(WIN32)
163 return 2048;
164 #else
165 struct rlimit limitFD;
166 if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
167 // If the current soft limit is already higher, don't raise it
168 if (limitFD.rlim_cur != RLIM_INFINITY && std::cmp_less(limitFD.rlim_cur, min_fd)) {
169 const auto current_limit{limitFD.rlim_cur};
170 static_assert(std::in_range<rlim_t>(std::numeric_limits<int>::max()));
171 limitFD.rlim_cur = static_cast<rlim_t>(min_fd);
172 // Don't raise soft limit beyond hard limit
173 if ((limitFD.rlim_max != RLIM_INFINITY) && (limitFD.rlim_cur > limitFD.rlim_max)) {
174 limitFD.rlim_cur = limitFD.rlim_max;
175 }
176 if (current_limit != limitFD.rlim_cur) {
177 setrlimit(RLIMIT_NOFILE, &limitFD);
178 getrlimit(RLIMIT_NOFILE, &limitFD);
179 }
180 }
181 // Check the (possibly raised) current soft limit against the special
182 // value of RLIM_INFINITY. Some platforms implement this as the maximum
183 // uint64, others as int64 (-1). Avoid casting even if the return type
184 // is changed to uint64_t. We also cap unlikely but possible values
185 // that would overflow int.
186 if (limitFD.rlim_cur == RLIM_INFINITY ||
187 std::cmp_greater_equal(limitFD.rlim_cur, std::numeric_limits<int>::max())) {
188 return std::numeric_limits<int>::max();
189 }
190 return static_cast<int>(limitFD.rlim_cur);
191 }
192 return min_fd; // getrlimit failed, assume it's fine
193 #endif
194 }
195
196 /**
197 * this function tries to make a particular range of a file allocated (corresponding to disk space)
198 * it is advisory, and the range specified in the arguments will never contain live data
199 */
200 void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length)
201 {
202 #if defined(WIN32)
203 // Windows-specific version
204 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
205 LARGE_INTEGER nFileSize;
206 int64_t nEndPos = (int64_t)offset + length;
207 nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF;
208 nFileSize.u.HighPart = nEndPos >> 32;
209 SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN);
210 SetEndOfFile(hFile);
211 #elif defined(__APPLE__)
212 // OSX specific version
213 // NOTE: Contrary to other OS versions, the OSX version assumes that
214 // NOTE: offset is the size of the file.
215 fstore_t fst;
216 fst.fst_flags = F_ALLOCATECONTIG;
217 fst.fst_posmode = F_PEOFPOSMODE;
218 fst.fst_offset = 0;
219 fst.fst_length = length; // mac os fst_length takes the # of free bytes to allocate, not desired file size
220 fst.fst_bytesalloc = 0;
221 if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) {
222 fst.fst_flags = F_ALLOCATEALL;
223 fcntl(fileno(file), F_PREALLOCATE, &fst);
224 }
225 ftruncate(fileno(file), static_cast<off_t>(offset) + length);
226 #else
227 #if defined(HAVE_POSIX_FALLOCATE)
228 // Version using posix_fallocate
229 off_t nEndPos = (off_t)offset + length;
230 if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return;
231 #endif
232 // Fallback version
233 // TODO: just write one byte per block
234 static const char buf[65536] = {};
235 if (fseek(file, offset, SEEK_SET)) {
236 return;
237 }
238 while (length > 0) {
239 unsigned int now = 65536;
240 if (length < now)
241 now = length;
242 fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway
243 length -= now;
244 }
245 #endif
246 }
247
248 #ifdef WIN32
249 fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
250 {
251 WCHAR pszPath[MAX_PATH] = L"";
252
253 if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) {
254 return fs::path(pszPath);
255 }
256
257 LogError("SHGetSpecialFolderPathW() failed, could not obtain requested path.");
258 return fs::path("");
259 }
260 #endif
261
262 bool RenameOver(fs::path src, fs::path dest)
263 {
264 std::error_code error;
265 fs::rename(src, dest, error);
266 return !error;
267 }
268
269 /**
270 * Ignores exceptions thrown by create_directories if the requested directory exists.
271 * Specifically handles case where path p exists, but it wasn't possible for the user to
272 * write to the parent directory.
273 */
274 bool TryCreateDirectories(const fs::path& p)
275 {
276 try {
277 return fs::create_directories(p);
278 } catch (const fs::filesystem_error&) {
279 if (!fs::exists(p) || !fs::is_directory(p))
280 throw;
281 }
282
283 // create_directories didn't create the directory, it had to have existed already
284 return false;
285 }
286
287 std::string PermsToSymbolicString(fs::perms p)
288 {
289 std::string perm_str(9, '-');
290
291 auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
292 if ((p & required_perm) != fs::perms::none) {
293 perm_str[pos] = letter;
294 }
295 };
296
297 set_perm(0, fs::perms::owner_read, 'r');
298 set_perm(1, fs::perms::owner_write, 'w');
299 set_perm(2, fs::perms::owner_exec, 'x');
300 set_perm(3, fs::perms::group_read, 'r');
301 set_perm(4, fs::perms::group_write, 'w');
302 set_perm(5, fs::perms::group_exec, 'x');
303 set_perm(6, fs::perms::others_read, 'r');
304 set_perm(7, fs::perms::others_write, 'w');
305 set_perm(8, fs::perms::others_exec, 'x');
306
307 return perm_str;
308 }
309
310 std::optional<fs::perms> InterpretPermString(const std::string& s)
311 {
312 if (s == "owner") {
313 return fs::perms::owner_read | fs::perms::owner_write;
314 } else if (s == "group") {
315 return fs::perms::owner_read | fs::perms::owner_write |
316 fs::perms::group_read;
317 } else if (s == "all") {
318 return fs::perms::owner_read | fs::perms::owner_write |
319 fs::perms::group_read |
320 fs::perms::others_read;
321 } else {
322 return std::nullopt;
323 }
324 }
325
326 bool IsDirWritable(const fs::path& dir_path)
327 {
328 // Attempt to create a tmp file in the directory
329 if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path)));
330 FastRandomContext rng;
331 const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64()));
332
333 const char* mode;
334 #ifdef __MINGW64__
335 mode = "w"; // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
336 #else
337 mode = "wx";
338 #endif
339
340 if (const auto created{fsbridge::fopen(tmp, mode)}) {
341 std::fclose(created);
342 std::error_code ec;
343 fs::remove(tmp, ec); // clean up, ignore errors
344 return true;
345 }
346 return false;
347 }
348
349 #ifdef __APPLE__
350 FSType GetFilesystemType(const fs::path& path)
351 {
352 if (struct statfs fs_info; statfs(path.c_str(), &fs_info)) {
353 return FSType::ERROR;
354 } else if (std::string_view{fs_info.f_fstypename} == "exfat") {
355 return FSType::EXFAT;
356 }
357 return FSType::OTHER;
358 }
359 #endif
360