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