bc/bc/system/file/win/Stacked.cpp

816 lines
22 KiB
C++

#include "bc/file/Defines.hpp"
#include "bc/file/File.hpp"
#include "bc/file/Path.hpp"
#include "bc/system/file/Stacked.hpp"
#include "bc/system/file/win/WinFile.hpp"
#include "bc/Debug.hpp"
#include "bc/Memory.hpp"
#include "bc/String.hpp"
#include <algorithm>
/********************************
* Begin Win32 Stacked functions *
*********************************/
namespace Blizzard {
namespace System_File {
namespace Stacked {
bool SetWorkingDirectory(FileParms* parms) {
BLIZZARD_ASSERT(parms->filename);
return ::SetCurrentDirectory(parms->filename) != 0;
}
bool Close(FileParms* parms) {
auto file = parms->stream;
BLIZZARD_ASSERT(file != nullptr);
::CloseHandle(file->filehandle);
Memory::Free(file);
return true;
}
bool GetWorkingDirectory(FileParms* parms) {
if (!parms->directory || !parms->directorySize) {
// System_File::FileError(8)
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto size = static_cast<DWORD>(parms->directorySize);
auto directory = static_cast<LPSTR>(parms->directory);
::GetCurrentDirectory(size, directory);
return true;
}
bool ProcessDirFast(FileParms* parms) {
// Get parameters
auto unkflag = parms->flag;
auto directory = parms->filename;
auto callback = parms->callback;
auto param = parms->param;
// Set up walk parameters
File::ProcessDirParms processDirParms;
processDirParms.root = directory;
processDirParms.param = param;
constexpr uint32_t formatSize = BC_FILE_MAX_PATH * 2;
char formatted[formatSize] = "";
// Tack on a backslash as well as an asterisk.
String::Format(formatted, formatSize, "%s\\*", processDirParms.root);
// Convert potentially large path to universal format
BC_FILE_PATH(path);
if (!File::Path::MakeNativePath(formatted, path, formatSize)) {
return false;
}
//
WIN32_FIND_DATA findData;
auto hFindFile = ::FindFirstFile(formatted, &findData);
if (hFindFile == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_INVALID_ARGUMENT, "Win32 ProcessDirFast - %s", directory);
return false;
}
BC_FILE_PATH(currentPath);
processDirParms.item = currentPath;
while (true) {
// Ignore .. and .
if (findData.cFileName[0] != '.' || findData.cFileName[1] && (findData.cFileName[1] != '.' || findData.cFileName[2])) {
String::Copy(currentPath, findData.cFileName, BC_FILE_MAX_PATH);
processDirParms.itemIsDirectory = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
if (!callback(processDirParms)) {
::FindClose(hFindFile);
return true;
}
if (!::FindNextFile(hFindFile, &findData)) {
break;
}
}
}
::FindClose(hFindFile);
return false;
}
bool Exists(FileParms* parms) {
auto filepath = parms->filename;
if (!filepath) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative filepathNative(filepath);
auto dwFileAttributes = ::GetFileAttributes(static_cast<LPCSTR>(filepathNative.Str()));
if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
parms->info->attributes = 0;
return false;
}
uint32_t fileAttributes = WinFile::AttributesToBC(dwFileAttributes);
parms->info->attributes = fileAttributes;
return fileAttributes & BC_FILE_ATTRIBUTE_NORMAL;
}
bool Flush(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE ) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto status = ::FlushFileBuffers(file->filehandle);
return status != 0;
}
bool GetFileInfo(FileParms* parms) {
auto file = parms->stream;
auto filepath = parms->filename;
auto info = parms->info;
if (filepath == nullptr && file == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
// StreamRecord* file may be used to supply storage of file info. (StreamRecord*->info)
if (file) {
BLIZZARD_ASSERT(file->filehandle != INVALID_HANDLE_VALUE);
if (info == nullptr) {
// telling this function to save file info into StreamRecord
info = &file->info;
}
}
if (filepath) {
// Fetch info based on filepath.
static File::FileInfo s_noinfo = {};
if (info == nullptr) {
info = &s_noinfo;
}
File::Path::QuickNative filepathNative(filepath);
WIN32_FILE_ATTRIBUTE_DATA fileAttributeData = {};
// Read attributes
if (!::GetFileAttributesEx(filepathNative.Str(), GetFileExInfoStandard, &fileAttributeData)) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_INVALID_HANDLE, "Win32 GetFileInfo - GetFileAttributesExA failed");
return false;
}
return WinFile::AttributeFileInfoToBC(&fileAttributeData, info);
}
// Fetch info using opened file handle
auto filehandle = file->filehandle;
BY_HANDLE_FILE_INFORMATION byHandleInfo;
if (!::GetFileInformationByHandle(filehandle, &byHandleInfo)) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_HANDLE);
return false;
}
file->hasInfo = true;
return WinFile::HandleFileInfoToBC(&byHandleInfo, info);
}
bool GetFreeSpace(FileParms* parms) {
auto filename = parms->filename;
if (filename == nullptr || *filename == '\0') {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto len = String::Length(filename) + 2;
BC_FILE_PATH(systemPath);
// UNC convert
File::Path::MakeNativePath(filename, systemPath, BC_FILE_MAX_PATH);
// GetDiskFreeSpaceExA will fail without a trailing backslash
File::Path::ForceTrailingSeparator(systemPath, BC_FILE_MAX_PATH, BC_FILE_SYSTEM_PATH_SEPARATOR);
ULARGE_INTEGER freeBytesAvailableToCaller = {};
ULARGE_INTEGER totalNumberOfBytes = {};
ULARGE_INTEGER totalNumberOfFreeBytes = {};
if (!GetDiskFreeSpaceExA(systemPath, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
parms->size64 = static_cast<uint64_t>(freeBytesAvailableToCaller.QuadPart);
return true;
}
bool GetPos(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
LONG high = 0;
DWORD low = ::SetFilePointer(file->filehandle, 0, &high, FILE_CURRENT);
if (low == -1 && GetLastError()) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_HANDLE);
return false;
}
ULARGE_INTEGER ul = {};
ul.LowPart = low;
ul.HighPart = high;
parms->position = ul.QuadPart;
return true;
}
bool GetRootChars(FileParms* parms) {
char cVar1 = '\0';
int32_t counter = 0;
int32_t size = 0;
char *buffer = nullptr;
const char *path = nullptr;
char unixPathFast[256] = {0};
const char *end = nullptr;
path = parms->filename;
counter = String::Length(path);
size = counter + 1;
if (size > 256) {
buffer = reinterpret_cast<char *>(Memory::Allocate(size));
} else {
buffer = unixPathFast;
}
// Invalid without error
if (size < 0x2) {
parms->end = 0;
parms->beginning = 0;
} else {
File::Path::MakeUnivPath(path, buffer, size);
path = buffer + 1;
// Get DOS canonical path
if (buffer[1] == ':') {
cVar1 = buffer[2];
parms->beginning = 0;
parms->end = (cVar1 == '/') + 2;
} else {
// Get UNC path
if ((*buffer == '/') && (buffer[1] == '/')) {
counter = 0;
do {
path = strchr(path + 1, '$');
if (path == nullptr) {
parms->end = size;
parms->beginning = 0;
if (buffer != unixPathFast) {
Memory::Free(buffer);
}
return true;
}
counter = counter + 1;
} while (counter < 2);
parms->end = intptr_t(path) + (1 - (intptr_t)buffer);
} else {
parms->end = 0;
}
parms->beginning = 0;
}
}
if (buffer != unixPathFast) {
Memory::Free(buffer);
}
return true;
}
bool IsAbsolutePath(FileParms* parms) {
auto path = parms->filename;
if (!path) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto first = path[0];
if (
// UNC
(
((first == '\\') || (first == '/')) &&
((path[1] == '\\' || (path[1] == '/')))
) &&
(path[2] != '\0')) {
return true;
}
// DOS canonical
if (isalpha(first) && (path[1] == ':')) {
if (((path[2] == '\\') || (path[2] == '/')) && (path[3] != '\0')) {
return true;
}
}
return false;
}
bool IsReadOnly(FileParms* parms) {
auto filepath = parms->filename;
if (!filepath) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative filepathNative(filepath);
auto dwFileAttributes = ::GetFileAttributes(filepathNative.Str());
if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
return (dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0;
}
bool MakeAbsolutePath(FileParms* parms) {
BC_FILE_PATH(full);
auto path = parms->filename;
if (path == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
::_fullpath(full, path, BC_FILE_MAX_PATH);
File::Path::ForceTrailingSeparator(full, BC_FILE_MAX_PATH, BC_FILE_SYSTEM_PATH_SEPARATOR);
String::Copy(parms->directory, full, parms->directorySize);
return true;
}
bool CreateDirectory(FileParms* parms) {
constexpr size_t temp_size = 300;
auto path = parms->filename;
auto recursive = parms->flag != 0;
char temp_path[temp_size] = {0};
String::Copy(temp_path, path, temp_size);
File::Path::ForceTrailingSeparator(temp_path, temp_size, '\\');
if (recursive) {
auto p = temp_path;
if (isalpha(p[0]) && p[1] == ':') {
p += 2;
}
// Loop through path and call CreateDirectory on path elements
for (auto p = temp_path + 1; *p != '\0'; p++) {
if (*p == '\\') {
*p = 0;
auto attributes = ::GetFileAttributes(temp_path);
// test path
if (attributes == INVALID_FILE_ATTRIBUTES) {
// path does not exist, create directory
if (!::CreateDirectory(temp_path, nullptr)) {
return false;
}
} else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
// not a directory
return false;
}
*p = '\\';
}
}
} else {
// Create only the supplied directory.
if (::GetFileAttributes(temp_path) == INVALID_FILE_ATTRIBUTES) {
if (!::CreateDirectory(temp_path, nullptr)) {
if (::GetLastError() != ERROR_ALREADY_EXISTS) {
return false;
}
}
}
}
return true;
}
bool Move(FileParms* parms) {
auto source = parms->filename;
auto destination = parms->destination;
File::Path::QuickNative sourceNative(source);
File::Path::QuickNative destinationNative(destination);
BOOL ok = ::MoveFile(sourceNative.Str(), destinationNative.Str());
return ok != 0;
}
bool Copy(FileParms* parms) {
auto source = parms->filename;
auto destination = parms->destination;
auto overwrite = parms->flag;
// file pointers
File::StreamRecord* st_source = nullptr;
File::StreamRecord* st_destination = nullptr;
// Flags for working with src and dst files
auto flag_source = BC_FILE_OPEN_READ | BC_FILE_OPEN_MUST_EXIST;
auto flag_destination = BC_FILE_OPEN_WRITE | BC_FILE_OPEN_CREATE;
if (!overwrite) {
// User commands that we cannot overwrite. Fail if file already exists
flag_destination |= BC_FILE_OPEN_MUST_NOT_EXIST;
} else {
// We are supposed to overwrite, so truncate the file to 0 bytes.
flag_destination |= BC_FILE_OPEN_TRUNCATE;
}
// Open source file to be copied
if (!File::Open(source, flag_source, st_source)) {
// FileError(2)
BC_FILE_SET_ERROR(BC_FILE_ERROR_FILE_NOT_FOUND);
return false;
}
// Open (or create if it doesn't exist) destination file
if (!File::Open(destination, flag_destination, st_destination)) {
File::Close(st_source);
// FileError(4)
BC_FILE_SET_ERROR(BC_FILE_ERROR_BUSY);
return false;
}
// Determine buffer copy size
auto sz_source = File::GetFileInfo(st_source)->size;
// copybuffer size upper limit is BC_FILE_SYSTEM_COPYBUFFER_SIZE
uint64_t sz_copybuffer = std::min(sz_source, static_cast<uint64_t>(BC_FILE_SYSTEM_COPYBUFFER_SIZE));
auto u8_copybuffer = reinterpret_cast<uint8_t*>(Memory::Allocate(sz_copybuffer));
// Loop through the source file, reading segments into copybuffer
for (uint64_t index = 0; index < sz_source; index += sz_copybuffer) {
// How many bytes to read
size_t sz_bytesToRead = static_cast<size_t>(sz_source - std::min(index+sz_copybuffer, sz_source));
size_t sz_bytesRead = 0;
size_t sz_bytesWritten = 0;
// Read data segment into copybuffer
auto status = File::Read(st_source, u8_copybuffer, sz_bytesToRead, &sz_bytesRead);
if (status) {
// Write copied segment to destination file
status = File::Write(st_destination, u8_copybuffer, sz_bytesRead, &sz_bytesWritten);
}
if (!status) {
// either read or write failed: cleanup copy buffer
Memory::Free(u8_copybuffer);
// close files
File::Close(st_source);
File::Close(st_destination);
// Delete malformed file
File::Delete(destination);
// return bad status, but without creating a file error.
return false;
}
}
Memory::Free(u8_copybuffer);
// close files
File::Close(st_source);
File::Close(st_destination);
// Success!
return true;
}
bool Open(FileParms* parms) {
// Path convert
auto path = parms->filename;
File::Path::QuickNative pathNative(path);
// Open file HANDLE
auto flags = parms->flag;
bool nocache = parms->mode & File::Mode::nocache;
HANDLE handle = WinFile::Open(pathNative.Str(), flags, nocache);
if (handle == INVALID_HANDLE_VALUE) {
DWORD err = 0;
if (err = ::GetLastError()) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_GENERIC_FAILURE, "Win32 Open %s", parms->filename);
}
return false;
}
// Successfully opened file handle. Allocate StreamRecord + path str at the end.
auto recordSize = (sizeof(File::StreamRecord) - File::StreamRecord::s_padPath) + (1 + pathNative.Size());
auto fileData = Memory::Allocate(recordSize);
// Memory could not be allocated
if (fileData == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_OOM);
return false;
}
// Clear extra data
String::MemFill(fileData, recordSize, 0);
// Populate fields
auto file = reinterpret_cast<File::StreamRecord*>(fileData);
file->flags = flags;
file->filehandle = handle;
String::Copy(file->path, path, pathNative.Size());
File::GetFileInfo(file);
parms->stream = file;
return true;
}
bool Read(File::StreamRecord* file, void* data, int64_t offset, size_t* bytes) {
// File descriptor must be initialized!
BLIZZARD_ASSERT(file != nullptr && file->filehandle != INVALID_HANDLE_VALUE);
if (bytes == nullptr || *bytes == 0) {
return true;
}
if (offset > -1) {
if (!File::SetPos(file, offset, BC_FILE_SEEK_START)) {
return false;
}
}
DWORD dwRead = 0;
BOOL ok = ::ReadFile(file->filehandle, data, *bytes, &dwRead, nullptr);
if (ok == 0) {
// append any Win32 failure to the error log
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_BAD_FILE, "Win32 Read %p - %s", GetLastError(), file->path);
return false;
}
*bytes = static_cast<size_t>(dwRead);
return true;
}
bool Read(FileParms* parms) {
return System_File::Stacked::Read(parms->stream, parms->param, -1, &parms->size);
}
bool ReadP(FileParms* parms) {
return System_File::Stacked::Read(parms->stream, parms->param, parms->position, &parms->size);
}
bool RemoveDirectory(FileParms* parms) {
auto dirpath = parms->filename;
if (dirpath == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative pathNative(dirpath);
BOOL ok = ::RemoveDirectory(dirpath);
return ok != 0;
}
bool SetCacheMode(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
if (file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_HANDLE);
return false;
}
File::Path::QuickNative pathNative(file->path);
// Close handle and reopen
CloseHandle(file->filehandle);
bool nocache = (parms->mode & File::Mode::nocache) != 0;
file->filehandle = WinFile::Open(pathNative.Str(), file->flags, nocache);
if (file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR_MSG(4, "Win32 SetCacheMode - %s", file->path);
return false;
}
if (nocache) {
parms->mode &= ~(File::Mode::nocache);
}
return true;
}
bool SetEOF(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
// Save current pos
int64_t originalpos = 0;
if (!File::GetPos(file, originalpos)) {
return false;
}
int64_t newpos = parms->position;
auto whence = parms->whence;
if (!File::SetPos(file, newpos, whence)) {
return false;
}
BOOL b = ::SetEndOfFile(file->filehandle);
if (!b) {
BC_FILE_SET_ERROR_MSG(4, "Win32 SetEOF - %s", file->path);
return false;
}
// Restore original pos
return File::SetPos(file, originalpos, BC_FILE_SEEK_START);
}
bool SetAttributes(FileParms* parms) {
auto info = parms->info;
if (info == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto mode = parms->mode;
auto attributes = info->attributes;
int32_t status = 0;
auto file = parms->stream;
auto path = parms->filename;
if (mode & File::Mode::settimes) {
auto modTime = info->modificationTime;
FILETIME ft = WinFile::PackTime(Time::ToWinFiletime(modTime));
if (::SetFileTime(file->filehandle, nullptr, nullptr, &ft)) {
file->info.modificationTime = modTime;
parms->mode &= ~(File::Mode::settimes);
}
}
if (mode & File::Mode::setperms) {
File::Path::QuickNative pathNative(parms->filename);
DWORD dwAttributes = WinFile::AttributesToWin(info->attributes);
BOOL ok = ::SetFileAttributes(pathNative.Str(), dwAttributes);
if (!ok) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
parms->mode &= ~(File::Mode::setperms);
}
return true;
}
bool SetPos(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto offset = parms->position;
auto whence = parms->whence;
auto offsetLow = static_cast<LONG>(offset);
auto offsetHigh = static_cast<LONG>(offset >> 32);
DWORD res = ::SetFilePointer(file->filehandle, offsetLow, &offsetHigh, static_cast<DWORD>(whence));
if (res == INVALID_SET_FILE_POINTER) {
res = GetLastError();
if (res != 0) {
return false;
}
}
return true;
}
bool Delete(FileParms* parms) {
auto deletePath = parms->filename;
if (deletePath == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative deletePathNative(deletePath);
BOOL ok = ::DeleteFile(deletePathNative.Str());
return ok != 0;
}
bool Write(FileParms* parms) {
auto file = parms->stream;
auto data = parms->param;
auto size = parms->size;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
DWORD numberOfBytesWritten = 0;
BOOL ok = WriteFile(file->filehandle, data, size, &numberOfBytesWritten, nullptr);
return ok != 0;
}
bool WriteP(FileParms* parms) {
auto file = parms->stream;
auto data = parms->param;
auto position = parms->position;
auto size = parms->size;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
int64_t origpos = 0;
if (!File::GetPos(file, origpos)) {
return false;
}
if (!File::SetPos(file, position, BC_FILE_SEEK_START)) {
return false;
}
DWORD numberOfBytesWritten = 0;
BOOL ok = WriteFile(file->filehandle, data, size, &numberOfBytesWritten, nullptr);
if (ok == 0) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_BAD_FILE, "Win32 Write - %s", file->path);
return false;
}
return File::SetPos(file, origpos, BC_FILE_SEEK_START);
}
} // namespace Stacked
} // namespace System_File
} // namespace Blizzard
/********************************
* End of Win32 Stacked functions *
*********************************/