Coverage Report

Created: 2026-04-20 22:07

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