Coverage Report

Created: 2025-09-19 18:31

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