Coverage Report

Created: 2025-06-10 13:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 <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>
41
#include <shlobj.h>
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
44.3k
{
55
44.3k
    LOCK(cs_dir_locks);
56
44.3k
    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
44.3k
    if (dir_locks.count(fs::PathToString(pathLockFile))) {
  Branch (59:9): [True: 0, False: 44.3k]
60
0
        return LockResult::Success;
61
0
    }
62
63
    // Create empty lock file if it doesn't exist.
64
44.3k
    if (auto created{fsbridge::fopen(pathLockFile, "a")}) {
  Branch (64:14): [True: 44.3k, False: 0]
65
44.3k
        std::fclose(created);
66
44.3k
    } else {
67
0
        return LockResult::ErrorWrite;
68
0
    }
69
44.3k
    auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile);
70
44.3k
    if (!lock->TryLock()) {
  Branch (70:9): [True: 0, False: 44.3k]
71
0
        LogError("Error while attempting to lock directory %s: %s\n", fs::PathToString(directory), lock->GetReason());
72
0
        return LockResult::ErrorLock;
73
0
    }
74
44.3k
    if (!probe_only) {
  Branch (74:9): [True: 22.1k, False: 22.1k]
75
        // Lock successful and we're not just probing, put it into the map
76
22.1k
        dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock));
77
22.1k
    }
78
44.3k
    return LockResult::Success;
79
44.3k
}
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
0
{
89
0
    LOCK(cs_dir_locks);
90
0
    dir_locks.clear();
91
0
}
92
93
bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes)
94
124k
{
95
124k
    constexpr uint64_t min_disk_space = 52428800; // 50 MiB
96
97
124k
    uint64_t free_bytes_available = fs::space(dir).available;
98
124k
    return free_bytes_available >= min_disk_space + additional_bytes;
99
124k
}
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
123k
{
110
123k
    if (fflush(file) != 0) { // harmless if redundantly called
  Branch (110:9): [True: 0, False: 123k]
111
0
        LogPrintf("fflush failed: %s\n", 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
        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
123k
    if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync
  Branch (126:9): [True: 0, False: 123k]
  Branch (126:41): [True: 0, False: 0]
127
0
        LogPrintf("fdatasync failed: %s\n", SysErrorString(errno));
128
0
        return false;
129
0
    }
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
123k
    return true;
137
123k
}
138
139
void DirectoryCommit(const fs::path& dirname)
140
58.4k
{
141
58.4k
#ifndef WIN32
142
58.4k
    FILE* file = fsbridge::fopen(dirname, "r");
143
58.4k
    if (file) {
  Branch (143:9): [True: 58.4k, False: 0]
144
58.4k
        fsync(fileno(file));
145
58.4k
        fclose(file);
146
58.4k
    }
147
58.4k
#endif
148
58.4k
}
149
150
bool TruncateFile(FILE* file, unsigned int length)
151
0
{
152
#if defined(WIN32)
153
    return _chsize(_fileno(file), length) == 0;
154
#else
155
0
    return ftruncate(fileno(file), length) == 0;
156
0
#endif
157
0
}
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
11.0k
{
165
#if defined(WIN32)
166
    return 2048;
167
#else
168
11.0k
    struct rlimit limitFD;
169
11.0k
    if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
  Branch (169:9): [True: 11.0k, False: 0]
170
11.0k
        if (limitFD.rlim_cur < (rlim_t)nMinFD) {
  Branch (170:13): [True: 0, False: 11.0k]
171
0
            limitFD.rlim_cur = nMinFD;
172
0
            if (limitFD.rlim_cur > limitFD.rlim_max)
  Branch (172:17): [True: 0, False: 0]
173
0
                limitFD.rlim_cur = limitFD.rlim_max;
174
0
            setrlimit(RLIMIT_NOFILE, &limitFD);
175
0
            getrlimit(RLIMIT_NOFILE, &limitFD);
176
0
        }
177
11.0k
        return limitFD.rlim_cur;
178
11.0k
    }
179
0
    return nMinFD; // getrlimit failed, assume it's fine
180
11.0k
#endif
181
11.0k
}
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
33.2k
{
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
33.2k
#if defined(HAVE_POSIX_FALLOCATE)
215
    // Version using posix_fallocate
216
33.2k
    off_t nEndPos = (off_t)offset + length;
217
33.2k
    if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return;
  Branch (217:9): [True: 33.2k, False: 0]
218
0
#endif
219
    // Fallback version
220
    // TODO: just write one byte per block
221
0
    static const char buf[65536] = {};
222
0
    if (fseek(file, offset, SEEK_SET)) {
  Branch (222:9): [True: 0, False: 0]
223
0
        return;
224
0
    }
225
0
    while (length > 0) {
  Branch (225:12): [True: 0, False: 0]
226
0
        unsigned int now = 65536;
227
0
        if (length < now)
  Branch (227:13): [True: 0, False: 0]
228
0
            now = length;
229
0
        fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway
230
0
        length -= now;
231
0
    }
232
0
#endif
233
0
}
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
66.5k
{
251
66.5k
    std::error_code error;
252
66.5k
    fs::rename(src, dest, error);
253
66.5k
    return !error;
254
66.5k
}
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
44.3k
{
263
44.3k
    try {
264
44.3k
        return fs::create_directories(p);
265
44.3k
    } catch (const fs::filesystem_error&) {
266
0
        if (!fs::exists(p) || !fs::is_directory(p))
  Branch (266:13): [True: 0, False: 0]
  Branch (266:31): [True: 0, False: 0]
267
0
            throw;
268
0
    }
269
270
    // create_directories didn't create the directory, it had to have existed already
271
0
    return false;
272
44.3k
}
273
274
std::string PermsToSymbolicString(fs::perms p)
275
11.0k
{
276
11.0k
    std::string perm_str(9, '-');
277
278
99.8k
    auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
279
99.8k
        if ((p & required_perm) != fs::perms::none) {
  Branch (279:13): [True: 22.1k, False: 77.6k]
280
22.1k
            perm_str[pos] = letter;
281
22.1k
        }
282
99.8k
    };
283
284
11.0k
    set_perm(0, fs::perms::owner_read,   'r');
285
11.0k
    set_perm(1, fs::perms::owner_write,  'w');
286
11.0k
    set_perm(2, fs::perms::owner_exec,   'x');
287
11.0k
    set_perm(3, fs::perms::group_read,   'r');
288
11.0k
    set_perm(4, fs::perms::group_write,  'w');
289
11.0k
    set_perm(5, fs::perms::group_exec,   'x');
290
11.0k
    set_perm(6, fs::perms::others_read,  'r');
291
11.0k
    set_perm(7, fs::perms::others_write, 'w');
292
11.0k
    set_perm(8, fs::perms::others_exec,  'x');
293
294
11.0k
    return perm_str;
295
11.0k
}
296
297
std::optional<fs::perms> InterpretPermString(const std::string& s)
298
0
{
299
0
    if (s == "owner") {
  Branch (299:9): [True: 0, False: 0]
300
0
        return fs::perms::owner_read | fs::perms::owner_write;
301
0
    } else if (s == "group") {
  Branch (301:16): [True: 0, False: 0]
302
0
        return fs::perms::owner_read | fs::perms::owner_write |
303
0
               fs::perms::group_read;
304
0
    } else if (s == "all") {
  Branch (304:16): [True: 0, False: 0]
305
0
        return fs::perms::owner_read | fs::perms::owner_write |
306
0
               fs::perms::group_read |
307
0
               fs::perms::others_read;
308
0
    } else {
309
0
        return std::nullopt;
310
0
    }
311
0
}