/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: FS.NT.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "FS.hpp" #include "FS.Generic.hpp" #include #if !defined(_AURUNTIME_GENERICFS) namespace Aurora::IO::FS { static const AuUInt64 kFileCopyBlock = 0xFFFF; // 64KiB bool _MkDir(const AuROString &str) { return CreateDirectoryW(Locale::ConvertFromUTF8(str).c_str(), NULL); } struct ReadDirStructure : IReadDir { HANDLE hFind {INVALID_HANDLE_VALUE}; WIN32_FIND_DATAW ffd; bool bFirstTick {true}; bool bDead {false}; StatEx stat; AuString sPath; AuUInt32 uErrorCount {}; AuList errorPaths; ~ReadDirStructure() { ::FindClose(this->hFind); } virtual AuUInt32 GetErrorCount() override { return this->uErrorCount; } virtual AuList GetErrorPaths() override { if (this->uErrorCount) { return { this->sPath }; } else { return {}; } } virtual StatEx *Next() override { AU_DEBUG_MEMCRUNCH; if (!AuExchange(this->bFirstTick, false)) { if (::FindNextFileW(this->hFind, &this->ffd) == 0) { if (GetLastError() != ERROR_NO_MORE_FILES) { this->uErrorCount++; } return {}; } } if (this->bDead) { return {}; } if (this->ffd.cFileName == std::wstring(L".") || this->ffd.cFileName == std::wstring(L"..")) { return Next(); } stat.bExists = true; stat.fileName = Locale::ConvertFromWChar(this->ffd.cFileName); if (stat.fileName.empty()) { this->bDead = true; this->uErrorCount++; return {}; } stat.path = NormalizePathRet(this->sPath + stat.fileName); if (stat.path.empty()) { this->bDead = true; this->uErrorCount++; return {}; } stat.bExistsDirectory = this->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; stat.bExistsFile = !stat.bExistsDirectory; stat.bSymLink = this->ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT; stat.uSize = AuUInt64(this->ffd.nFileSizeLow) | (AuUInt64(this->ffd.nFileSizeHigh) << 32); stat.createdNs = Time::ConvertTimestampNs(this->ffd.ftCreationTime); stat.modifiedNs = Time::ConvertTimestampNs(this->ffd.ftLastWriteTime); stat.accessedNs = Time::ConvertTimestampNs(this->ffd.ftLastAccessTime); return &stat; } }; AUKN_SYM AuSPtr ReadDir(const AuROString &string) { if (string.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return {}; } auto pObj = AuMakeShared(); if (!pObj) { SysPushErrorMem(); return {}; } if (!AuIOFS::NormalizePath(pObj->sPath, string)) { SysPushErrorMem(); return {}; } if (!AuTryInsert(pObj->sPath, '\\')) { SysPushErrorMem(); return {}; } pObj->hFind = ::FindFirstFileW(AuLocale::ConvertFromUTF8(pObj->sPath + "*").c_str(), &pObj->ffd); if (INVALID_HANDLE_VALUE == pObj->hFind) { SysPushErrorIO(); return {}; } return pObj; } AUKN_SYM bool FilesInDirectory(const AuROString &string, AuList &files) { auto itr = ReadDir(string); if (!itr) { return false; } // SECURITY(): if next fails, its indistinguishable from end of file list, and will return true. it kinda sucks while (auto stat = itr->Next()) { if (!stat->bExistsFile) { continue; } if (!AuTryInsert(files, stat->fileName)) { return false; } } return true; } AUKN_SYM bool DirsInDirectory(const AuROString &string, AuList &dirs) { auto itr = ReadDir(string); if (!itr) { return false; } // SECURITY(): if next fails, its indistinguishable from end of file list, and will return true. it kinda sucks while (auto stat = itr->Next()) { if (!stat->bExistsDirectory) { continue; } if (!AuTryInsert(dirs, stat->fileName)) { return false; } } return true; } AUKN_SYM bool ReadFile(const AuROString &path, AuByteBuffer &buffer) { std::wstring win32Path; LARGE_INTEGER length; bool status; AuMemoryViewWrite writeView; size_t offset; if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } status = false; offset = 0; win32Path = Locale::ConvertFromUTF8(NormalizePathRet(path)); if (win32Path.empty()) { SysPushErrorMemory(); return false; } auto fileHandle = Win32Open(win32Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, false, OPEN_EXISTING, 0, 0); if (fileHandle == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION) { RuntimeWaitForSecondaryTick(); fileHandle = Win32Open(win32Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, false, OPEN_EXISTING, 0, 0); } if (fileHandle == INVALID_HANDLE_VALUE) { SysPushErrorIO("Couldn't open handle: {}", path); return false; } if (!::GetFileSizeEx(fileHandle, &length)) { SysPushErrorIO(); goto out; } if (length.QuadPart == 0) { AuWin32CloseHandle(fileHandle); return true; } writeView = buffer.GetOrAllocateLinearWriteable(length.QuadPart); if (!writeView) { SysPushErrorMem(); goto out; } while (length.QuadPart) { DWORD read; int blockSize = AuMin(static_cast(kFileCopyBlock), static_cast(length.QuadPart)); if (!::ReadFile(fileHandle, buffer.writePtr, blockSize, &read, NULL)) { AuLogWarn("ReadFile IO Error: 0x{:x} {}", GetLastError(), path); SysPushErrorIO(); goto out; } buffer.writePtr += read; offset += read; length.QuadPart -= read; } status = true; out: AuWin32CloseHandle(fileHandle); return status; } AUKN_SYM bool FileExists(const AuROString &path) { try { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto translatedPath = Locale::ConvertFromUTF8(NormalizePathRet(path)); if (translatedPath.empty()) { SysPushErrorMemory(); return false; } DWORD dwAttrib = ::GetFileAttributesW(translatedPath.c_str()); return ((dwAttrib != INVALID_FILE_ATTRIBUTES) && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); } catch (...) { SysPushErrorCatch(); return false; } } AUKN_SYM bool DirExists(const AuROString &path) { try { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto translatedPath = Locale::ConvertFromUTF8(NormalizePathRet(path)); if (translatedPath.empty()) { SysPushErrorMemory(); return false; } DWORD dwAttrib = ::GetFileAttributesW(translatedPath.c_str()); return ((dwAttrib != INVALID_FILE_ATTRIBUTES) && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); } catch (...) { SysPushErrorCatch(); return false; } } AUKN_SYM bool DirMk(const AuROString &path) { try { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto translatedPath = NormalizePathRet(path); if (translatedPath.empty()) { SysPushErrorMemory(); return false; } return CreateDirectories(translatedPath, false); } catch (...) { SysPushErrorCatch(); return false; } } AUKN_SYM bool Remove(const AuROString &path) { try { if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } Aurora::RuntimeWaitForSecondaryTick(); auto translatedPath = Locale::ConvertFromUTF8(NormalizePathRet(path)); if (translatedPath.empty()) { SysPushErrorMemory(); return false; } DWORD dwAttrib = ::GetFileAttributesW(translatedPath.c_str()); if (dwAttrib == INVALID_FILE_ATTRIBUTES) { SysPushErrorIO("Couldn't open file ({}) for removal", path); return false; } if (dwAttrib & FILE_ATTRIBUTE_READONLY) { SysPushErrorIO("Couldn't open file ({}) for removal (read-only)", path); return false; } if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) { if (::RemoveDirectoryW(translatedPath.c_str())) { return true; } } else { if (::DeleteFileW(translatedPath.c_str())) { return true; } } return false; } catch (...) { SysPushErrorCatch(); return false; } } AUKN_SYM bool Relink(const AuROString &src, const AuROString &dest) { try { if (src.empty() || dest.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathSrcExpanded = Locale::ConvertFromUTF8(NormalizePathRet(src)); auto pathExpandedA = NormalizePathRet(dest); auto pathExpanded = Locale::ConvertFromUTF8(pathExpandedA); if (pathSrcExpanded.empty() || pathExpanded.empty()) { SysPushErrorMemory(); return false; } CreateDirectories(pathExpandedA, true); Aurora::RuntimeWaitForSecondaryTick(); return ::MoveFileW(pathSrcExpanded.c_str(), pathExpanded.c_str()); } catch (...) { return false; } } AUKN_SYM bool Copy(const AuROString &src, const AuROString &dest) { try { if (src.empty() || dest.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto pathSrcExpanded = Locale::ConvertFromUTF8(NormalizePathRet(src)); auto pathExpandedA = NormalizePathRet(dest); auto pathExpanded = Locale::ConvertFromUTF8(pathExpandedA); if (pathSrcExpanded.empty() || pathExpanded.empty()) { SysPushErrorMemory(); return false; } CreateDirectories(pathExpandedA, true); return ::CopyFileW(pathSrcExpanded.c_str(), pathExpanded.c_str(), true); } catch (...) { SysPushErrorCatch(); return false; } } AUKN_SYM bool StatFile(const AuROString &path, Stat &stat) { WIN32_FILE_ATTRIBUTE_DATA data; stat = {}; if (path.empty()) { SysPushErrorArg("Cannot open an IO handle to the provided empty path"); return false; } auto translatedPath = Locale::ConvertFromUTF8(NormalizePathRet(path)); if (translatedPath.empty()) { SysPushErrorMemory(); return false; } if (!::GetFileAttributesExW(translatedPath.c_str(), GetFileExInfoStandard, &data)) { stat.bExists = false; return false; } stat.bExists = true; stat.bExistsDirectory = data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; stat.bExistsFile = !stat.bExistsDirectory; stat.bSymLink = data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT; stat.uSize = AuUInt64(data.nFileSizeLow) | (AuUInt64(data.nFileSizeHigh) << 32); stat.createdNs = Time::ConvertTimestampNs(data.ftCreationTime); stat.modifiedNs = Time::ConvertTimestampNs(data.ftLastWriteTime); stat.accessedNs = Time::ConvertTimestampNs(data.ftLastAccessTime); return true; } } #endif