// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2023 The Limenka developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WIN32 // for posix_fallocate, in cmake/introspection.cmake we check if it is present after this #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include #include #include #else #include /* For _get_osfhandle, _chsize */ #include /* For SHGetSpecialFolderPathW */ #include #endif // WIN32 /** Mutex to protect dir_locks. */ static GlobalMutex cs_dir_locks; /** A map that contains all the currently held directory locks. After * successful locking, these will be held here until the global destructor * cleans them up and thus automatically unlocks them, or ReleaseDirectoryLocks * is called. */ static std::map> dir_locks GUARDED_BY(cs_dir_locks); namespace util { LockResult LockDirectory(const fs::path& directory, const fs::path& lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; // If a lock for this directory already exists in the map, don't try to re-lock it if (dir_locks.count(fs::PathToString(pathLockFile))) { return LockResult::Success; } // Create empty lock file if it doesn't exist. if (auto created{fsbridge::fopen(pathLockFile, "a")}) { std::fclose(created); } else { return LockResult::ErrorWrite; } auto lock = std::make_unique(pathLockFile); if (!lock->TryLock()) { LogError("Error while attempting to lock directory %s: %s\n", fs::PathToString(directory), lock->GetReason()); return LockResult::ErrorLock; } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); } return LockResult::Success; } } // namespace util void UnlockDirectory(const fs::path& directory, const fs::path& lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); } void ReleaseDirectoryLocks() { LOCK(cs_dir_locks); dir_locks.clear(); } bool CheckDiskSpace(const fs::path& dir, uint64_t additional_bytes) { constexpr uint64_t min_disk_space = 52428800; // 50 MiB uint64_t free_bytes_available = fs::space(dir).available; return free_bytes_available >= min_disk_space + additional_bytes; } std::streampos GetFileSize(const char* path, std::streamsize max) { std::ifstream file{path, std::ios::binary}; file.ignore(max); return file.gcount(); } bool FileCommit(FILE* file) { if (fflush(file) != 0) { // harmless if redundantly called LogError("fflush failed: %s", SysErrorString(errno)); return false; } #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); if (FlushFileBuffers(hFile) == 0) { LogError("FlushFileBuffers failed: %s", Win32ErrorString(GetLastError())); return false; } #elif defined(__APPLE__) && defined(F_FULLFSYNC) if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { // Manpage says "value other than -1" is returned on success LogError("fcntl F_FULLFSYNC failed: %s", SysErrorString(errno)); return false; } #elif HAVE_FDATASYNC if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { // Ignore EINVAL for filesystems that don't support sync LogError("fdatasync failed: %s", SysErrorString(errno)); return false; } #else if (fsync(fileno(file)) != 0 && errno != EINVAL) { LogError("fsync failed: %s", SysErrorString(errno)); return false; } #endif return true; } void DirectoryCommit(const fs::path& dirname) { #ifndef WIN32 FILE* file = fsbridge::fopen(dirname, "r"); if (file) { fsync(fileno(file)); fclose(file); } #endif } bool TruncateFile(FILE* file, unsigned int length) { #if defined(WIN32) return _chsize(_fileno(file), length) == 0; #else return ftruncate(fileno(file), length) == 0; #endif } /** * this function tries to raise the file descriptor limit to the requested number. * It returns the actual file descriptor limit (which may be more or less than nMinFD) */ int RaiseFileDescriptorLimit(int nMinFD) { Assert(nMinFD >= 0); #if defined(WIN32) return 2048; #else struct rlimit limitFD; if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { // If the current soft limit is already higher, don't raise it if (limitFD.rlim_cur != RLIM_INFINITY && std::cmp_less(limitFD.rlim_cur, nMinFD)) { const auto current_limit{limitFD.rlim_cur}; limitFD.rlim_cur = std::in_range(nMinFD) ? static_cast(nMinFD) : limitFD.rlim_max; // Don't raise soft limit beyond hard limit if (limitFD.rlim_max != RLIM_INFINITY && ( limitFD.rlim_cur > limitFD.rlim_max ) ) { limitFD.rlim_cur = limitFD.rlim_max; } if (current_limit != limitFD.rlim_cur) { setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } } // Check the (possibly raised) current soft limit against the special // value of RLIM_INFINITY. Some platforms implement this as the maximum // uint64, others as int64 (-1). Avoid casting even if the return type // is changed to uint64_t. We also cap unlikely but possible values // that would overflow int. if (limitFD.rlim_cur == RLIM_INFINITY || std::cmp_greater_equal(limitFD.rlim_cur, std::numeric_limits::max())) { return std::numeric_limits::max(); } return static_cast(limitFD.rlim_cur); } return nMinFD; // getrlimit failed, assume it's fine #endif } /** * this function tries to make a particular range of a file allocated (corresponding to disk space) * it is advisory, and the range specified in the arguments will never contain live data */ void AllocateFileRange(FILE* file, unsigned int offset, unsigned int length) { #if defined(WIN32) // Windows-specific version HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); LARGE_INTEGER nFileSize; int64_t nEndPos = (int64_t)offset + length; if (GetFileSizeEx(hFile, &nFileSize) && (int64_t{nFileSize.u.HighPart} << 32 | nFileSize.u.LowPart) <= nEndPos) { nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; nFileSize.u.HighPart = nEndPos >> 32; if (SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN)) { SetEndOfFile(hFile); } } #elif 0 // OSX specific version // NOTE: Contrary to other OS versions, the OSX version assumes that // NOTE: offset is the size of the file. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; fst.fst_length = length; // mac os fst_length takes the # of free bytes to allocate, not desired file size fst.fst_bytesalloc = 0; if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { fst.fst_flags = F_ALLOCATEALL; fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), static_cast(offset) + length); #else #if defined(HAVE_POSIX_FALLOCATE) // Version using posix_fallocate if (0 == posix_fallocate(fileno(file), offset, length)) return; #endif // Fallback version // TODO: just write one byte per block uint8_t buf[65536]; if (fseek(file, offset, SEEK_SET)) { return; } clearerr(file); while (length > 0) { unsigned int now = 65536; if (length < now) now = length; const size_t rlen = fread(buf, 1, now, file); if (rlen < now) { if (ferror(file)) { // Don't clobber anything, just give up clearerr(file); return; } memset(&buf[rlen], 0, now - rlen); if (0 != fseek(file, -rlen, SEEK_CUR)) { return; } } fwrite(buf, 1, now, file); // allowed to fail; this function is advisory anyway length -= now; } #endif } FILE* AdviseSequential(FILE *file) { #ifdef _POSIX_C_SOURCE # if _POSIX_C_SOURCE >= 200112L // Since this whole thing is advisory anyway, we can ignore any errors // encountered up to and including the posix_fadvise call. However, we must // rewind the file to the appropriate position if we've changed the seek // offset. if (file == nullptr) { return nullptr; } const int fd = fileno(file); if (fd == -1) { return file; } const off_t start = lseek(fd, 0, SEEK_CUR); if (start == -1) { return file; } posix_fadvise(fd, start, 0, POSIX_FADV_WILLNEED); posix_fadvise(fd, start, 0, POSIX_FADV_SEQUENTIAL); # endif #endif return file; } int CloseAndUncache(FILE *file) { #ifdef _POSIX_C_SOURCE # if _POSIX_C_SOURCE >= 200112L // Ignore any errors up to and including the posix_fadvise call since it's // advisory. if (file != nullptr) { const int fd = fileno(file); if (fd != -1) { const off_t end = lseek(fd, 0, SEEK_END); if (end != (off_t)-1) { posix_fadvise(fd, 0, end, POSIX_FADV_DONTNEED); } } } # endif #endif return std::fclose(file); } #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) { WCHAR pszPath[MAX_PATH] = L""; if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogError("SHGetSpecialFolderPathW() failed, could not obtain requested path."); return fs::path(""); } #endif bool RenameOver(fs::path src, fs::path dest) { std::error_code error; fs::rename(src, dest, error); return !error; } /** * Ignores exceptions thrown by create_directories if the requested directory exists. * Specifically handles case where path p exists, but it wasn't possible for the user to * write to the parent directory. */ bool TryCreateDirectories(const fs::path& p) { try { return fs::create_directories(p); } catch (const fs::filesystem_error&) { if (!fs::exists(p) || !fs::is_directory(p)) throw; } // create_directories didn't create the directory, it had to have existed already return false; } std::string PermsToSymbolicString(fs::perms p) { std::string perm_str(9, '-'); auto set_perm = [&](size_t pos, fs::perms required_perm, char letter, char else_letter = '\0') { if ((p & required_perm) != fs::perms::none) { perm_str[pos] = letter; } else if (else_letter) { perm_str[pos] = else_letter; } }; set_perm(0, fs::perms::owner_read, 'r'); set_perm(1, fs::perms::owner_write, 'w'); if ((p & fs::perms::owner_exec) != fs::perms::none) { set_perm(2, fs::perms::set_uid, 's', 'x'); } else { set_perm(2, fs::perms::set_uid, 'S'); } set_perm(3, fs::perms::group_read, 'r'); set_perm(4, fs::perms::group_write, 'w'); if ((p & fs::perms::group_exec) != fs::perms::none) { set_perm(5, fs::perms::set_gid, 's', 'x'); } else { set_perm(5, fs::perms::set_gid, 'S'); } set_perm(6, fs::perms::others_read, 'r'); set_perm(7, fs::perms::others_write, 'w'); if ((p & fs::perms::others_exec) != fs::perms::none) { set_perm(8, fs::perms::sticky_bit, 't', 'x'); } else { set_perm(8, fs::perms::sticky_bit, 'T'); } return perm_str; } static std::optional StringToOctal(const std::string& str) { unsigned ret = 0; for (char c : str) { if (c < '0' || c > '7') return std::nullopt; ret = (ret << 3) | (c - '0'); } return ret; } static auto ConvertPermsToOctal(const std::string& str) noexcept -> std::optional { if ((str.length() == 3) || (str.length() == 4)) return StringToOctal(str); return std::nullopt; } std::optional InterpretPermString(const std::string& s) { if (s == "owner") { return fs::perms::owner_read | fs::perms::owner_write; } else if (s == "group") { return fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read; } else if (s == "all") { return fs::perms::owner_read | fs::perms::owner_write | fs::perms::group_read | fs::perms::others_read; } else if (auto octal_perms = ConvertPermsToOctal(s)) { return static_cast(*octal_perms); } else { return std::nullopt; } } bool IsDirWritable(const fs::path& dir_path) { // Attempt to create a tmp file in the directory if (!fs::is_directory(dir_path)) throw std::runtime_error(strprintf("Path %s is not a directory", fs::PathToString(dir_path))); FastRandomContext rng; const auto tmp = dir_path / fs::PathFromString(strprintf(".tmp_%d", rng.rand64())); if (const auto created{fsbridge::fopen(tmp, "wbx")}) { std::fclose(created); std::error_code ec; fs::remove(tmp, ec); // clean up, ignore errors return true; } return false; } bool IsSymlink(const fs::path& path) { #ifdef WIN32 DWORD file_attrs = GetFileAttributesW(path.wstring().c_str()); if (file_attrs == INVALID_FILE_ATTRIBUTES) { throw fs::filesystem_error("Unable to get file attributes", fs::PathToString(path), std::make_error_code(std::errc::invalid_argument)); } return (file_attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0; #else return fs::is_symlink(path); #endif }