Coverage Report

Created: 2026-07-23 20:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/util/fs_helpers.cpp
Line
Count
Source
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
4.58k
{
55
4.58k
    LOCK(cs_dir_locks);
56
4.58k
    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
4.58k
    if (dir_locks.contains(fs::PathToString(pathLockFile))) {
60
2
        return LockResult::Success;
61
2
    }
62
63
    // Create empty lock file if it doesn't exist.
64
4.58k
    if (auto created{fsbridge::fopen(pathLockFile, "a")}) {
65
4.58k
        std::fclose(created);
66
4.58k
    } else {
67
2
        return LockResult::ErrorWrite;
68
2
    }
69
4.58k
    auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile);
70
4.58k
    if (!lock->TryLock()) {
71
4
        LogError("Error while attempting to lock directory %s: %s\n", fs::PathToString(directory), lock->GetReason());
72
4
        return LockResult::ErrorLock;
73
4
    }
74
4.58k
    if (!probe_only) {
75
        // Lock successful and we're not just probing, put it into the map
76
2.28k
        dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock));
77
2.28k
    }
78
4.58k
    return LockResult::Success;
79
4.58k
}
80
} // namespace util
81
void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name)
82
0
{
83
0
    LOCK(cs_dir_locks);
84
0
    dir_locks.erase(fs::PathToString(directory / lockfile_name));
85
0
}
86
87
void ReleaseDirectoryLocks()
88
3
{
89
3
    LOCK(cs_dir_locks);
90
3
    dir_locks.clear();
91
3
}
92
93
bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes)
94
10.3k
{
95
10.3k
    constexpr uint64_t min_disk_space{50_MiB};
96
97
10.3k
    uint64_t free_bytes_available = fs::space(dir).available;
98
10.3k
    return free_bytes_available >= min_disk_space + additional_bytes;
99
10.3k
}
100
101
std::streampos GetFileSize(const char* path, std::streamsize max)
102
0
{
103
0
    std::ifstream file{path, std::ios::binary};
104
0
    file.ignore(max);
105
0
    return file.gcount();
106
0
}
107
108
bool FileCommit(FILE* file)
109
9.46k
{
110
9.46k
    if (fflush(file) != 0) { // harmless if redundantly called
111
0
        LogError("fflush failed: %s", SysErrorString(errno));
112
0
        return false;
113
0
    }
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
9.46k
    if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync
127
0
        LogError("fdatasync failed: %s", SysErrorString(errno));
128
0
        return false;
129
0
    }
130
#else
131
    if (fsync(fileno(file)) != 0 && errno != EINVAL) {
132
        LogError("fsync failed: %s", SysErrorString(errno));
133
        return false;
134
    }
135
#endif
136
9.46k
    return true;
137
9.46k
}
138
139
void DirectoryCommit(const fs::path& dirname)
140
6.90k
{
141
6.90k
#ifndef WIN32
142
6.90k
    FILE* file = fsbridge::fopen(dirname, "r");
143
6.90k
    if (file) {
144
6.90k
        fsync(fileno(file));
145
6.90k
        fclose(file);
146
6.90k
    }
147
6.90k
#endif
148
6.90k
}
149
150
bool TruncateFile(FILE* file, unsigned int length)
151
68
{
152
#if defined(WIN32)
153
    return _chsize(_fileno(file), length) == 0;
154
#else
155
68
    return ftruncate(fileno(file), length) == 0;
156
68
#endif
157
68
}
158
159
int RaiseFileDescriptorLimit(int min_fd)
160
1.85k
{
161
1.85k
    Assert(min_fd >= 0);
162
#if defined(WIN32)
163
    return 2048;
164
#else
165
1.85k
    struct rlimit limitFD;
166
1.85k
    if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
167
        // If the current soft limit is already higher, don't raise it
168
1.85k
        if (limitFD.rlim_cur != RLIM_INFINITY && std::cmp_less(limitFD.rlim_cur, min_fd)) {
169
0
            const auto current_limit{limitFD.rlim_cur};
170
0
            static_assert(std::in_range<rlim_t>(std::numeric_limits<int>::max()));
171
0
            limitFD.rlim_cur = static_cast<rlim_t>(min_fd);
172
            // Don't raise soft limit beyond hard limit
173
0
            if ((limitFD.rlim_max != RLIM_INFINITY) && (limitFD.rlim_cur > limitFD.rlim_max)) {
174
0
                limitFD.rlim_cur = limitFD.rlim_max;
175
0
            }
176
0
            if (current_limit != limitFD.rlim_cur) {
177
0
                setrlimit(RLIMIT_NOFILE, &limitFD);
178
0
                getrlimit(RLIMIT_NOFILE, &limitFD);
179
0
            }
180
0
        }
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
1.85k
        if (limitFD.rlim_cur == RLIM_INFINITY ||
187
1.85k
            std::cmp_greater_equal(limitFD.rlim_cur, std::numeric_limits<int>::max())) {
188
0
            return std::numeric_limits<int>::max();
189
0
        }
190
1.85k
        return static_cast<int>(limitFD.rlim_cur);
191
1.85k
    }
192
0
    return min_fd; // getrlimit failed, assume it's fine
193
1.85k
#endif
194
1.85k
}
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
985
{
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
985
#if defined(HAVE_POSIX_FALLOCATE)
228
    // Version using posix_fallocate
229
985
    off_t nEndPos = (off_t)offset + length;
230
985
    if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return;
231
0
#endif
232
    // Fallback version
233
    // TODO: just write one byte per block
234
0
    static const char buf[65536] = {};
235
0
    if (fseek(file, offset, SEEK_SET)) {
236
0
        return;
237
0
    }
238
0
    while (length > 0) {
239
0
        unsigned int now = 65536;
240
0
        if (length < now)
241
0
            now = length;
242
0
        fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway
243
0
        length -= now;
244
0
    }
245
0
#endif
246
0
}
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
4.98k
{
264
4.98k
    std::error_code error;
265
4.98k
    fs::rename(src, dest, error);
266
4.98k
    return !error;
267
4.98k
}
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
4.29k
{
276
4.29k
    try {
277
4.29k
        return fs::create_directories(p);
278
4.29k
    } catch (const fs::filesystem_error&) {
279
2
        if (!fs::exists(p) || !fs::is_directory(p))
280
2
            throw;
281
2
    }
282
283
    // create_directories didn't create the directory, it had to have existed already
284
0
    return false;
285
4.29k
}
286
287
std::string PermsToSymbolicString(fs::perms p)
288
1.12k
{
289
1.12k
    std::string perm_str(9, '-');
290
291
10.0k
    auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
292
10.0k
        if ((p & required_perm) != fs::perms::none) {
293
2.24k
            perm_str[pos] = letter;
294
2.24k
        }
295
10.0k
    };
296
297
1.12k
    set_perm(0, fs::perms::owner_read,   'r');
298
1.12k
    set_perm(1, fs::perms::owner_write,  'w');
299
1.12k
    set_perm(2, fs::perms::owner_exec,   'x');
300
1.12k
    set_perm(3, fs::perms::group_read,   'r');
301
1.12k
    set_perm(4, fs::perms::group_write,  'w');
302
1.12k
    set_perm(5, fs::perms::group_exec,   'x');
303
1.12k
    set_perm(6, fs::perms::others_read,  'r');
304
1.12k
    set_perm(7, fs::perms::others_write, 'w');
305
1.12k
    set_perm(8, fs::perms::others_exec,  'x');
306
307
1.12k
    return perm_str;
308
1.12k
}
309
310
std::optional<fs::perms> InterpretPermString(const std::string& s)
311
3
{
312
3
    if (s == "owner") {
313
1
        return fs::perms::owner_read | fs::perms::owner_write;
314
2
    } else if (s == "group") {
315
1
        return fs::perms::owner_read | fs::perms::owner_write |
316
1
               fs::perms::group_read;
317
1
    } else if (s == "all") {
318
1
        return fs::perms::owner_read | fs::perms::owner_write |
319
1
               fs::perms::group_read |
320
1
               fs::perms::others_read;
321
1
    } else {
322
0
        return std::nullopt;
323
0
    }
324
3
}
325
326
bool IsDirWritable(const fs::path& dir_path)
327
1.11k
{
328
    // Attempt to create a tmp file in the directory
329
1.11k
    if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path)));
330
1.11k
    FastRandomContext rng;
331
1.11k
    const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64()));
332
333
1.11k
    const char* mode;
334
#ifdef __MINGW64__
335
    mode = "w"; // Temporary workaround for https://github.com/bitcoin/bitcoin/issues/30210
336
#else
337
1.11k
    mode = "wx";
338
1.11k
#endif
339
340
1.11k
    if (const auto created{fsbridge::fopen(tmp, mode)}) {
341
1.11k
        std::fclose(created);
342
1.11k
        std::error_code ec;
343
1.11k
        fs::remove(tmp, ec); // clean up, ignore errors
344
1.11k
        return true;
345
1.11k
    }
346
0
    return false;
347
1.11k
}
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