Bitcoin Core 31.99.0
P2P Digital Currency
fs_helpers.cpp
Go to the documentation of this file.
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
51static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks);
52namespace util {
53LockResult LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only)
54{
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))) {
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 {
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());
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 }
79}
80} // namespace util
81void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name)
82{
84 dir_locks.erase(fs::PathToString(directory / lockfile_name));
85}
86
88{
90 dir_locks.clear();
91}
92
93bool 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
101std::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
108bool 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
139void 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
150bool 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
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
200void 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
249fs::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
262bool RenameOver(fs::path src, fs::path dest)
263{
264 std::error_code error;
265 fs::rename(src, dest, error);
266 return !error;
267}
268
274bool 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
287std::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
310std::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
326bool 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)));
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__
350FSType 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
#define Assert(val)
Identity function.
Definition: check.h:116
Fast randomness source.
Definition: random.h:386
uint64_t rand64() noexcept
Generate a random 64-bit integer.
Definition: random.h:404
Different type to mark Mutex at global scope.
Definition: sync.h:142
#define MAX_PATH
Definition: compat.h:81
static bool exists(const path &p)
Definition: fs.h:96
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:162
static path PathFromString(const std::string &string)
Convert byte string to path object.
Definition: fs.h:185
static GlobalMutex cs_dir_locks
Mutex to protect dir_locks.
Definition: fs_helpers.cpp:45
bool RenameOver(fs::path src, fs::path dest)
Rename src to dest.
Definition: fs_helpers.cpp:262
std::streampos GetFileSize(const char *path, std::streamsize max)
Get the size of a file by scanning it.
Definition: fs_helpers.cpp:101
void DirectoryCommit(const fs::path &dirname)
Sync directory contents.
Definition: fs_helpers.cpp:139
int RaiseFileDescriptorLimit(int min_fd)
Try to raise the file descriptor limit to the requested number.
Definition: fs_helpers.cpp:159
void ReleaseDirectoryLocks()
Release all directory locks.
Definition: fs_helpers.cpp:87
bool IsDirWritable(const fs::path &dir_path)
Check if a directory is writable by creating a temporary file on it.
Definition: fs_helpers.cpp:326
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by create_directories if the requested directory exists.
Definition: fs_helpers.cpp:274
void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length)
this function tries to make a particular range of a file allocated (corresponding to disk space) it i...
Definition: fs_helpers.cpp:200
bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes)
Definition: fs_helpers.cpp:93
std::optional< fs::perms > InterpretPermString(const std::string &s)
Interpret a custom permissions level string as fs::perms.
Definition: fs_helpers.cpp:310
bool TruncateFile(FILE *file, unsigned int length)
Definition: fs_helpers.cpp:150
static std::map< std::string, std::unique_ptr< fsbridge::FileLock > > dir_locks GUARDED_BY(cs_dir_locks)
A map that contains all the currently held directory locks.
std::string PermsToSymbolicString(fs::perms p)
Convert fs::perms to symbolic string of the form 'rwxrwxrwx'.
Definition: fs_helpers.cpp:287
bool FileCommit(FILE *file)
Ensure file contents are fully committed to disk, using a platform-specific feature analogous to fsyn...
Definition: fs_helpers.cpp:108
void UnlockDirectory(const fs::path &directory, const fs::path &lockfile_name)
Definition: fs_helpers.cpp:81
#define LogError(...)
Definition: log.h:127
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:23
LockResult
Definition: fs_helpers.h:69
LockResult LockDirectory(const fs::path &directory, const fs::path &lockfile_name, bool probe_only)
Definition: fs_helpers.cpp:53
#define LOCK(cs)
Definition: sync.h:268
std::string SysErrorString(int err)
Return system error string from errno value.
Definition: syserror.cpp:18
FastRandomContext rng
Definition: dbwrapper.cpp:414
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172