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