Skip to content
Bitcoin Roots

v29.4-roots.2 · Commit 2: [PATCH 02/21] runtime: preserve shared operator controls

src/util/fs_helpers.cpp

Before: src/util/fs_helpers.cpp · After: src/util/fs_helpers.cpp

File metadata
index 4d06afe144202245409a978d233c2959772a5bd5..1ae5d88fa874ce55dda1373683601dd5357bfe9c 100644

Download original patch · Release notes

Diff rows 1–312 of 312. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/util/fs_helpers.cpp CHANGED
@@ -8,15 +8,20 @@
8
8
  #include <bitcoin-build-config.h> // IWYU pragma: keep
9
9
 
10
10
  #include <logging.h>
11
+ #include <random.h>
11
12
  #include <sync.h>
13
+ #include <tinyformat.h>
14
+ #include <util/check.h>
12
15
  #include <util/fs.h>
13
16
  #include <util/syserror.h>
14
17
 
15
18
  #include <cerrno>
16
19
  #include <fstream>
20
+ #include <limits>
17
21
  #include <map>
18
22
  #include <memory>
19
23
  #include <optional>
24
+ #include <stdexcept>
20
25
  #include <string>
21
26
  #include <system_error>
22
27
  #include <utility>
@@ -39,6 +44,7 @@
39
44
  #else
40
45
  #include <io.h> /* For _get_osfhandle, _chsize */
41
46
  #include <shlobj.h> /* For SHGetSpecialFolderPathW */
47
+ #include <windows.h>
42
48
  #endif // WIN32
43
49
 
44
50
  /** Mutex to protect dir_locks. */
@@ -108,28 +114,28 @@ std::streampos GetFileSize(const char* path, std::streamsize max)
108
114
  bool FileCommit(FILE* file)
109
115
  {
110
116
  if (fflush(file) != 0) { // harmless if redundantly called
111
- LogPrintf("fflush failed: %s\n", SysErrorString(errno));
117
+ LogError("fflush failed: %s", SysErrorString(errno));
112
118
  return false;
113
119
  }
114
120
  #ifdef WIN32
115
121
  HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
116
122
  if (FlushFileBuffers(hFile) == 0) {
117
- LogPrintf("FlushFileBuffers failed: %s\n", Win32ErrorString(GetLastError()));
123
+ LogError("FlushFileBuffers failed: %s", Win32ErrorString(GetLastError()));
118
124
  return false;
119
125
  }
120
126
  #elif defined(__APPLE__) && defined(F_FULLFSYNC)
121
127
  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));
128
+ LogError("fcntl F_FULLFSYNC failed: %s", SysErrorString(errno));
123
129
  return false;
124
130
  }
125
131
  #elif HAVE_FDATASYNC
126
132
  if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync
127
- LogPrintf("fdatasync failed: %s\n", SysErrorString(errno));
133
+ LogError("fdatasync failed: %s", SysErrorString(errno));
128
134
  return false;
129
135
  }
130
136
  #else
131
137
  if (fsync(fileno(file)) != 0 && errno != EINVAL) {
132
- LogPrintf("fsync failed: %s\n", SysErrorString(errno));
138
+ LogError("fsync failed: %s", SysErrorString(errno));
133
139
  return false;
134
140
  }
135
141
  #endif
@@ -162,19 +168,38 @@ bool TruncateFile(FILE* file, unsigned int length)
162
168
  */
163
169
  int RaiseFileDescriptorLimit(int nMinFD)
164
170
  {
171
+ Assert(nMinFD >= 0);
165
172
  #if defined(WIN32)
166
173
  return 2048;
167
174
  #else
168
175
  struct rlimit limitFD;
169
176
  if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
170
- if (limitFD.rlim_cur < (rlim_t)nMinFD) {
171
- limitFD.rlim_cur = nMinFD;
172
- if (limitFD.rlim_cur > limitFD.rlim_max)
177
+ // If the current soft limit is already higher, don't raise it
178
+ if (limitFD.rlim_cur != RLIM_INFINITY && std::cmp_less(limitFD.rlim_cur, nMinFD)) {
179
+ const auto current_limit{limitFD.rlim_cur};
180
+ limitFD.rlim_cur = std::in_range<rlim_t>(nMinFD) ? static_cast<rlim_t>(nMinFD) : limitFD.rlim_max;
181
+ // Don't raise soft limit beyond hard limit
182
+ if (limitFD.rlim_max != RLIM_INFINITY && (
183
+ limitFD.rlim_cur > limitFD.rlim_max
184
+ )
185
+ ) {
173
186
  limitFD.rlim_cur = limitFD.rlim_max;
187
+ }
188
+ if (current_limit != limitFD.rlim_cur) {
174
189
  setrlimit(RLIMIT_NOFILE, &limitFD);
175
190
  getrlimit(RLIMIT_NOFILE, &limitFD);
191
+ }
176
192
  }
177
- return limitFD.rlim_cur;
193
+ // Check the (possibly raised) current soft limit against the special
194
+ // value of RLIM_INFINITY. Some platforms implement this as the maximum
195
+ // uint64, others as int64 (-1). Avoid casting even if the return type
196
+ // is changed to uint64_t. We also cap unlikely but possible values
197
+ // that would overflow int.
198
+ if (limitFD.rlim_cur == RLIM_INFINITY ||
199
+ std::cmp_greater_equal(limitFD.rlim_cur, std::numeric_limits<int>::max())) {
200
+ return std::numeric_limits<int>::max();
201
+ }
202
+ return static_cast<int>(limitFD.rlim_cur);
178
203
  }
179
204
  return nMinFD; // getrlimit failed, assume it's fine
180
205
  #endif
@@ -191,11 +216,14 @@ void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length)
191
216
  HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
192
217
  LARGE_INTEGER nFileSize;
193
218
  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__)
219
+ if (GetFileSizeEx(hFile, &nFileSize) && (int64_t{nFileSize.u.HighPart} << 32 | nFileSize.u.LowPart) <= nEndPos) {
220
+ nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF;
221
+ nFileSize.u.HighPart = nEndPos >> 32;
222
+ if (SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN)) {
223
+ SetEndOfFile(hFile);
224
+ }
225
+ }
226
+ #elif 0
199
227
  // OSX specific version
200
228
  // NOTE: Contrary to other OS versions, the OSX version assumes that
201
229
  // NOTE: offset is the size of the file.
@@ -213,25 +241,81 @@ void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length)
213
241
  #else
214
242
  #if defined(HAVE_POSIX_FALLOCATE)
215
243
  // Version using posix_fallocate
216
- off_t nEndPos = (off_t)offset + length;
217
- if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return;
244
+ if (0 == posix_fallocate(fileno(file), offset, length)) return;
218
245
  #endif
219
246
  // Fallback version
220
247
  // TODO: just write one byte per block
221
- static const char buf[65536] = {};
248
+ uint8_t buf[65536];
222
249
  if (fseek(file, offset, SEEK_SET)) {
223
250
  return;
224
251
  }
252
+ clearerr(file);
225
253
  while (length > 0) {
226
254
  unsigned int now = 65536;
227
255
  if (length < now)
228
256
  now = length;
257
+ const size_t rlen = fread(buf, 1, now, file);
258
+ if (rlen < now) {
259
+ if (ferror(file)) {
260
+ // Don't clobber anything, just give up
261
+ clearerr(file);
262
+ return;
263
+ }
264
+ memset(&buf[rlen], 0, now - rlen);
265
+ if (0 != fseek(file, -rlen, SEEK_CUR)) {
266
+ return;
267
+ }
268
+ }
229
269
  fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway
230
270
  length -= now;
231
271
  }
232
272
  #endif
233
273
  }
234
274
 
275
+ FILE* AdviseSequential(FILE *file) {
276
+ #ifdef _POSIX_C_SOURCE
277
+ # if _POSIX_C_SOURCE >= 200112L
278
+ // Since this whole thing is advisory anyway, we can ignore any errors
279
+ // encountered up to and including the posix_fadvise call. However, we must
280
+ // rewind the file to the appropriate position if we've changed the seek
281
+ // offset.
282
+ if (file == nullptr) {
283
+ return nullptr;
284
+ }
285
+ const int fd = fileno(file);
286
+ if (fd == -1) {
287
+ return file;
288
+ }
289
+ const off_t start = lseek(fd, 0, SEEK_CUR);
290
+ if (start == -1) {
291
+ return file;
292
+ }
293
+ posix_fadvise(fd, start, 0, POSIX_FADV_WILLNEED);
294
+ posix_fadvise(fd, start, 0, POSIX_FADV_SEQUENTIAL);
295
+ # endif
296
+ #endif
297
+ return file;
298
+ }
299
+
300
+ int CloseAndUncache(FILE *file) {
301
+ #ifdef _POSIX_C_SOURCE
302
+ # if _POSIX_C_SOURCE >= 200112L
303
+ // Ignore any errors up to and including the posix_fadvise call since it's
304
+ // advisory.
305
+ if (file != nullptr) {
306
+ const int fd = fileno(file);
307
+ if (fd != -1) {
308
+ const off_t end = lseek(fd, 0, SEEK_END);
309
+ if (end != (off_t)-1) {
310
+ posix_fadvise(fd, 0, end, POSIX_FADV_DONTNEED);
311
+ }
312
+ }
313
+ }
314
+ # endif
315
+ #endif
316
+ return std::fclose(file);
317
+ }
318
+
235
319
  #ifdef WIN32
236
320
  fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
237
321
  {
@@ -241,7 +325,7 @@ fs::path GetSpecialFolderPath(int nFolder, bool fCreate)
241
325
  return fs::path(pszPath);
242
326
  }
243
327
 
244
- LogPrintf("SHGetSpecialFolderPathW() failed, could not obtain requested path.\n");
328
+ LogError("SHGetSpecialFolderPathW() failed, could not obtain requested path.");
245
329
  return fs::path("");
246
330
  }
247
331
  #endif
@@ -275,25 +359,57 @@ std::string PermsToSymbolicString(fs::perms p)
275
359
  {
276
360
  std::string perm_str(9, '-');
277
361
 
278
- auto set_perm = [&](size_t pos, fs::perms required_perm, char letter) {
362
+ auto set_perm = [&](size_t pos, fs::perms required_perm, char letter, char else_letter = '\0') {
279
363
  if ((p & required_perm) != fs::perms::none) {
280
364
  perm_str[pos] = letter;
365
+ } else if (else_letter) {
366
+ perm_str[pos] = else_letter;
281
367
  }
282
368
  };
283
369
 
284
370
  set_perm(0, fs::perms::owner_read, 'r');
285
371
  set_perm(1, fs::perms::owner_write, 'w');
286
- set_perm(2, fs::perms::owner_exec, 'x');
372
+ if ((p & fs::perms::owner_exec) != fs::perms::none) {
373
+ set_perm(2, fs::perms::set_uid, 's', 'x');
374
+ } else {
375
+ set_perm(2, fs::perms::set_uid, 'S');
376
+ }
377
+
287
378
  set_perm(3, fs::perms::group_read, 'r');
288
379
  set_perm(4, fs::perms::group_write, 'w');
289
- set_perm(5, fs::perms::group_exec, 'x');
380
+ if ((p & fs::perms::group_exec) != fs::perms::none) {
381
+ set_perm(5, fs::perms::set_gid, 's', 'x');
382
+ } else {
383
+ set_perm(5, fs::perms::set_gid, 'S');
384
+ }
385
+
290
386
  set_perm(6, fs::perms::others_read, 'r');
291
387
  set_perm(7, fs::perms::others_write, 'w');
292
- set_perm(8, fs::perms::others_exec, 'x');
388
+ if ((p & fs::perms::others_exec) != fs::perms::none) {
389
+ set_perm(8, fs::perms::sticky_bit, 't', 'x');
390
+ } else {
391
+ set_perm(8, fs::perms::sticky_bit, 'T');
392
+ }
293
393
 
294
394
  return perm_str;
295
395
  }
296
396
 
397
+ static std::optional<unsigned> StringToOctal(const std::string& str)
398
+ {
399
+ unsigned ret = 0;
400
+ for (char c : str) {
401
+ if (c < '0' || c > '7') return std::nullopt;
402
+ ret = (ret << 3) | (c - '0');
403
+ }
404
+ return ret;
405
+ }
406
+
407
+ static auto ConvertPermsToOctal(const std::string& str) noexcept -> std::optional<unsigned>
408
+ {
409
+ if ((str.length() == 3) || (str.length() == 4)) return StringToOctal(str);
410
+ return std::nullopt;
411
+ }
412
+
297
413
  std::optional<fs::perms> InterpretPermString(const std::string& s)
298
414
  {
299
415
  if (s == "owner") {
@@ -305,7 +421,38 @@ std::optional<fs::perms> InterpretPermString(const std::string& s)
305
421
  return fs::perms::owner_read | fs::perms::owner_write |
306
422
  fs::perms::group_read |
307
423
  fs::perms::others_read;
424
+ } else if (auto octal_perms = ConvertPermsToOctal(s)) {
425
+ return static_cast<fs::perms>(*octal_perms);
308
426
  } else {
309
427
  return std::nullopt;
310
428
  }
311
429
  }
430
+
431
+ bool IsDirWritable(const fs::path& dir_path)
432
+ {
433
+ // Attempt to create a tmp file in the directory
434
+ if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path)));
435
+ FastRandomContext rng;
436
+ const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64()));
437
+
438
+ if (const auto created{fsbridge::fopen(tmp, "wbx")}) {
439
+ std::fclose(created);
440
+ std::error_code ec;
441
+ fs::remove(tmp, ec); // clean up, ignore errors
442
+ return true;
443
+ }
444
+ return false;
445
+ }
446
+
447
+ bool IsSymlink(const fs::path& path)
448
+ {
449
+ #ifdef WIN32
450
+ DWORD file_attrs = GetFileAttributesW(path.wstring().c_str());
451
+ if (file_attrs == INVALID_FILE_ATTRIBUTES) {
452
+ throw fs::filesystem_error("Unable to get file attributes", fs::PathToString(path), std::make_error_code(std::errc::invalid_argument));
453
+ }
454
+ return (file_attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
455
+ #else
456
+ return fs::is_symlink(path);
457
+ #endif
458
+ }