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