From bd65df59e996899faa730c3cfc862d4ce907ac4d Mon Sep 17 00:00:00 2001 From: superp00t Date: Wed, 9 Aug 2023 21:34:43 -0400 Subject: [PATCH] feat(file): implement filesystem utilities --- bc/CMakeLists.txt | 15 +- bc/Debug.hpp | 7 + bc/String.cpp | 235 +++++++- bc/String.hpp | 48 ++ bc/file/Defines.hpp | 52 ++ bc/file/File.cpp | 332 +++++++++++ bc/file/File.hpp | 54 ++ bc/file/Filesystem.cpp | 21 + bc/file/Filesystem.hpp | 66 +++ bc/file/Path.cpp | 194 +++++++ bc/file/Path.hpp | 51 ++ bc/file/Types.hpp | 76 +++ bc/os/File.cpp | 146 +++++ bc/os/File.hpp | 65 +++ bc/system/file/Defines.hpp | 13 + bc/system/file/Stacked.cpp | 291 ++++++++++ bc/system/file/Stacked.hpp | 85 +++ bc/system/file/System_File.hpp | 51 ++ bc/system/file/Types.hpp | 70 +++ bc/system/file/posix/Stacked.cpp | 602 ++++++++++++++++++++ bc/system/file/posix/System_File.cpp | 512 +++++++++++++++++ bc/system/file/win/Stacked.cpp | 811 +++++++++++++++++++++++++++ bc/system/file/win/Support.hpp | 12 + bc/system/file/win/System_File.cpp | 142 +++++ bc/system/file/win/WinFile.cpp | 181 ++++++ bc/system/file/win/WinFile.hpp | 36 ++ 26 files changed, 4163 insertions(+), 5 deletions(-) create mode 100644 bc/file/Defines.hpp create mode 100644 bc/file/File.cpp create mode 100644 bc/file/File.hpp create mode 100644 bc/file/Filesystem.cpp create mode 100644 bc/file/Filesystem.hpp create mode 100644 bc/file/Path.cpp create mode 100644 bc/file/Path.hpp create mode 100644 bc/file/Types.hpp create mode 100644 bc/os/File.cpp create mode 100644 bc/os/File.hpp create mode 100644 bc/system/file/Defines.hpp create mode 100644 bc/system/file/Stacked.cpp create mode 100644 bc/system/file/Stacked.hpp create mode 100644 bc/system/file/System_File.hpp create mode 100644 bc/system/file/Types.hpp create mode 100644 bc/system/file/posix/Stacked.cpp create mode 100644 bc/system/file/posix/System_File.cpp create mode 100644 bc/system/file/win/Stacked.cpp create mode 100644 bc/system/file/win/Support.hpp create mode 100644 bc/system/file/win/System_File.cpp create mode 100644 bc/system/file/win/WinFile.cpp create mode 100644 bc/system/file/win/WinFile.hpp diff --git a/bc/CMakeLists.txt b/bc/CMakeLists.txt index b518004..4ff3b2a 100644 --- a/bc/CMakeLists.txt +++ b/bc/CMakeLists.txt @@ -1,10 +1,23 @@ file(GLOB BC_SOURCES "*.cpp" "lock/*.cpp" + "os/*.cpp" + "file/*.cpp" "time/*.cpp" - "system/*.cpp" + "file/system/*.cpp" + "time/system/*.cpp" ) +if(DEFINED WHOA_SYSTEM_WIN) + file(GLOB BC_FILE_SYSTEM_SOURCES "file/system/win/*.cpp") +endif() + +if(DEFINED WHOA_SYSTEM_LINUX OR WHOA_SYSTEM_MAC) + file(GLOB BC_FILE_SYSTEM_SOURCES "file/system/posix/*.cpp") +endif() + +list(APPEND BC_SOURCES ${BC_FILE_SYSTEM_SOURCES}) + add_library(bc STATIC ${BC_SOURCES} ) diff --git a/bc/Debug.hpp b/bc/Debug.hpp index c350823..321f3bd 100644 --- a/bc/Debug.hpp +++ b/bc/Debug.hpp @@ -15,6 +15,13 @@ (void)0 #endif +#define BLIZZARD_VALIDATE(x, y, ...) \ + if (!(x)) { \ + Blizzard::Debug::Assert(!y, __FILE__, __LINE__); \ + return __VA_ARGS__; \ + } \ + (void)0 + namespace Blizzard { namespace Debug { diff --git a/bc/String.cpp b/bc/String.cpp index 20c0599..2efab25 100644 --- a/bc/String.cpp +++ b/bc/String.cpp @@ -1,7 +1,61 @@ #include "bc/String.hpp" +#include "bc/Debug.hpp" #include +#include +#include -int32_t Blizzard::String::Copy(char* dst, const char* src, size_t len) { +namespace Blizzard { +namespace String { + +int32_t Append(char* buf, const char* appended, size_t cap) { + const char* dx = nullptr; + int bytesAppended = 0; + char* outBytes = nullptr; + char* ceiling = nullptr; + char inByte = '\0'; + + if (!cap || !buf) { + return 0; + } + + ceiling = buf + (cap - 1); + + if (buf > ceiling) { + return 0; + } + + inByte = *buf; + outBytes = buf; + + while (inByte != '\0') { + outBytes = outBytes + 1; + if (ceiling < outBytes) { + return outBytes - buf; + } + inByte = *outBytes; + } + + if ((outBytes <= ceiling) && appended != nullptr) { + if (outBytes < ceiling) { + inByte = *appended; + + while (inByte != '\0') { + *outBytes = inByte; + outBytes = outBytes + 1; + if (ceiling <= outBytes) { + break; + } + appended = appended + 1; + inByte = *appended; + } + } + *outBytes = '\0'; + } + + return int32_t(reinterpret_cast(outBytes) - reinterpret_cast(buf)); +} + +int32_t Copy(char* dst, const char* src, size_t len) { if (!len || !dst) { return 0; } @@ -28,7 +82,7 @@ int32_t Blizzard::String::Copy(char* dst, const char* src, size_t len) { } v4 = (v5++)[1]; - } while ( v4 ); + } while (v4); result = v6 - dst; } else { @@ -41,7 +95,89 @@ int32_t Blizzard::String::Copy(char* dst, const char* src, size_t len) { return result; } -uint32_t Blizzard::String::Length(const char* str) { +// Find first occurence of char ch within string str +// returns ptr to first occurence, or null if it is not found +char* Find(char* str, char ch, size_t len) { + // Check if the string is null or empty. + if (str == nullptr || len == 0) { + return nullptr; + } + + auto ptr = str; + auto end = str + len; + + // Loop through the string, looking for the character. + while (ptr < end) { + if (*ptr == '\0' && ch != '\0') { + return nullptr; + } + + if (*ptr == ch) { + // Return the address of the first occurrence. + return ptr; + } + + // Increment the pointer. + ptr++; + } + + // The character was not found. + return nullptr; +} + +const char* FindFilename(const char* str) { + char ch = 0; + const char* result = nullptr; + auto ptr = str; + + if (str == nullptr) { + return ""; + } + + do { + do { + result = ptr; + ch = *str; + str = str + 1; + ptr = str; + } while (ch == '/'); + } while ((ch == '\\') || (ptr = result, ch != '\0')); + return result; +} + +// Format a string into char* dest, not exceeding uint32_t length. +void Format(char* dst, size_t capacity, const char* format, ...) { + if (!dst || capacity == 0) { + return; + } + + if (!format && capacity >= 1) { + *dst = '\0'; + return; + } + + auto formatNative = format; + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + constexpr size_t translatedSize = 2048; + + // POSIX formatting convention requires %ll for 64-bit printing + // Search-and-replace all instances of %I64 with %ll + char translated[translatedSize] = {0}; + + Translate(format, translated, translatedSize, "%I64", "%ll"); + + formatNative = translated; +#endif + va_list args; + va_start(args, format); + + vsnprintf(dst, capacity, formatNative, args); + + *dst = '\0'; +} + +uint32_t Length(const char* str) { if (str) { return strlen(str); } @@ -49,6 +185,97 @@ uint32_t Blizzard::String::Length(const char* str) { return 0; } -void Blizzard::String::MemFill(void* dst, uint32_t len, uint8_t fill) { +int32_t MemCompare(void* p1, void *p2, size_t len) { + return memcmp(p1, p2, len); +} + +void MemCopy(void* dst, const void* src, size_t len) { + memmove(dst, src, len); +} + +void MemFill(void* dst, uint32_t len, uint8_t fill) { memset(dst, fill, len); } + +void Translate(const char* src, char* dest, size_t destSize, const char* pattern, const char* replacement) { + if (dest == nullptr || pattern == nullptr || replacement == nullptr) { + return; + } + + if (src == nullptr) { + src = dest; + } + + // Cap-1 because we need to always affix a null character. + auto destCeiling = dest + destSize - 1; + auto srcCeiling = src + Length(src) + 1; + + auto patternLen = Length(pattern); + auto replacementLen = Length(replacement); + + // Current read pointer + auto srcPtr = src; + // Current write pointer + auto destPtr = dest; + + // Process string byte by byte + // Until dest ceiling is reached + while (destPtr < destCeiling && srcPtr < srcCeiling) { + auto byte = *srcPtr; + + if (byte == '\0') { + break; + } + + // If this byte is found at the start of pattern + if (byte == *pattern) { + // Calculate size of replacement bytes to copy + // Read size must not read outside of bounds + size_t copySize = replacementLen; + if ((destPtr + copySize) >= destCeiling) { + copySize = destCeiling - destPtr; + } + + // Look for the rest of the pattern + auto substring = strstr(srcPtr, pattern); + // If the read pointer is indeed pointing to an instance of the pattern, + if (substring == srcPtr) { + // Copy replacement data instead of original pattern. + MemCopy(destPtr, replacement, copySize); + destPtr += replacementLen; + srcPtr += patternLen; + continue; + } + } + + // copy a single byte + *destPtr = *srcPtr; + srcPtr++; + destPtr++; + } + + *destPtr = '\0'; +} + +void VFormat(char* dst, size_t capacity, const char* format, va_list args) { + char buffer[0x800] = {0}; + + auto formatNative = format; + +#if !defined(WHOA_SYSTEM_WIN) + Translate(format, buffer, 0x800, "%I64", "%ll"); + formatNative = buffer; +#endif + + if (format == nullptr) { + if (capacity != 0) { + *dst = '\0'; + } + } else { + vsnprintf(dst, capacity, formatNative, args); + dst[capacity - 1] = '\0'; + } +} + +} // namespace String +} // namespace Blizzard diff --git a/bc/String.hpp b/bc/String.hpp index aea8132..865dea9 100644 --- a/bc/String.hpp +++ b/bc/String.hpp @@ -3,15 +3,63 @@ #include #include +#include + +#if defined(WHOA_SYSTEM_WIN) +#define BC_FILE_SYSTEM_PATH_SEPARATOR '\\' +#else +#define BC_FILE_SYSTEM_PATH_SEPARATOR '/' +#endif namespace Blizzard { namespace String { +// Types + +template +class QuickFormat { + public: + char buffer[Cap]; + + QuickFormat(const char* format, ...); + const char* Str(); +}; + // Functions +int32_t Append(char* dst, const char* src, size_t cap); + int32_t Copy(char* dst, const char* src, size_t len); + +char* Find(char* str, char ch, size_t len); + +const char* FindFilename(const char* str); + +void Format(char* dest, size_t capacity, const char* format, ...); + uint32_t Length(const char* str); + void MemFill(void* dst, uint32_t len, uint8_t fill); +void MemCopy(void* dst, const void* src, size_t len); + +int32_t MemCompare(void* p1, void *p2, size_t len); + +void Translate(const char* src, char* dest, size_t destSize, const char* pattern, const char* replacement); + +void VFormat(char* dst, size_t capacity, const char* format, va_list args); + +template +QuickFormat::QuickFormat(const char* format, ...) { + va_list args; + va_start(args, format); + VFormat(this->buffer, Cap, format, args); +} + +template +const char* QuickFormat::Str() { + return static_cast(this->buffer); +} + } // namespace String } // namespace Blizzard diff --git a/bc/file/Defines.hpp b/bc/file/Defines.hpp new file mode 100644 index 0000000..715f07d --- /dev/null +++ b/bc/file/Defines.hpp @@ -0,0 +1,52 @@ +#ifndef BC_FILE_DEFINES_HPP +#define BC_FILE_DEFINES_HPP + +// How many bytes to when translating stacked filesystem names to large, native file paths +#define BC_FILE_MAX_PATH 1024 + +// File open/creation flags. +// See Blizzard::File::Open() for more info +#define BC_FILE_OPEN_READ 0x0001 +#define BC_FILE_OPEN_WRITE 0x0002 +#define BC_FILE_OPEN_SHARE_READ 0x0004 +#define BC_FILE_OPEN_SHARE_WRITE 0x0008 +#define BC_FILE_OPEN_TRUNCATE 0x0100 +#define BC_FILE_OPEN_ALWAYS 0x0200 +#define BC_FILE_OPEN_CREATE 0x0400 +#define BC_FILE_OPEN_MUST_NOT_EXIST 0x0800 +#define BC_FILE_OPEN_MUST_EXIST 0x1000 + +// File attribute flags +#define BC_FILE_ATTRIBUTE_READONLY 0x01 +#define BC_FILE_ATTRIBUTE_HIDDEN 0x02 +#define BC_FILE_ATTRIBUTE_SYSTEM 0x04 +#define BC_FILE_ATTRIBUTE_ARCHIVE 0x08 +#define BC_FILE_ATTRIBUTE_TEMPORARY 0x10 +#define BC_FILE_ATTRIBUTE_NORMAL 0x20 +#define BC_FILE_ATTRIBUTE_DIRECTORY 0x40 + +// File error codes +#define BC_FILE_ERROR_GENERIC_FAILURE 0 +#define BC_FILE_ERROR_ACCESS_DENIED 1 +#define BC_FILE_ERROR_FILE_NOT_FOUND 2 +#define BC_FILE_ERROR_BAD_FILE 3 +#define BC_FILE_ERROR_BUSY 4 +#define BC_FILE_ERROR_OOM 5 +#define BC_FILE_ERROR_INVALID_HANDLE 6 +#define BC_FILE_ERROR_END_OF_FILE 7 +#define BC_FILE_ERROR_INVALID_ARGUMENT 8 +#define BC_FILE_ERROR_UNIMPLEMENTED 9 +#define BC_FILE_ERROR_NO_SPACE_ON_DEVICE 11 + +// File SetPos whence +#define BC_FILE_SEEK_START 0 +#define BC_FILE_SEEK_CURRENT 1 +#define BC_FILE_SEEK_END 2 + +// Error handling utilities +#define BC_FILE_SET_ERROR(errorcode) ::Blizzard::File::SetLastError(static_cast(errorcode)) +#define BC_FILE_SET_ERROR_MSG(errorcode, pfmt, ...) \ + ::Blizzard::File::SetLastError(errorcode); \ + ::Blizzard::File::AddToLastErrorStack(errorcode, ::Blizzard::String::QuickFormat<1024>(pfmt, __VA_ARGS__).Str(), 1) + +#endif diff --git a/bc/file/File.cpp b/bc/file/File.cpp new file mode 100644 index 0000000..07005b9 --- /dev/null +++ b/bc/file/File.cpp @@ -0,0 +1,332 @@ +#include "bc/file/File.hpp" +#include "bc/file/Filesystem.hpp" +#include "bc/file/system/Stacked.hpp" + +#include +#include + +namespace Blizzard { +namespace File { + +// Functions + +void SetLastError(int32_t errorcode) { + // TODO +} + +void AddToLastErrorStack(int32_t errorcode, const char* msg, int32_t a3) { + // TODO: use proper logging + + char empty[] = ""; + + if (!msg) { + msg = empty; + } + + fprintf(stderr, "[bc/file] %" PRIi32 ": '%s'\n", errorcode, msg); +} + +// In BlizzardCore parlance, a file only exists if its path points to a regular file. (not a directory) +// i.e. directories are not considered to be file objects. +bool Exists(const char* path) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + FileInfo info = {}; + + System_File::Stacked::FileParms parms = {}; + parms.filename = path; + parms.info = &info; + + auto status = manager->Do(Filesystem::Call::Exists, &parms); + + return status; +} + +// Alias of Exists. +bool IsFile(const char* path) { + return Exists(path); +} + +// Calls Exists internally, only checking whether it has the directory attribute instead of normal attribute +bool IsDirectory(const char* path) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + FileInfo info = {}; + + System_File::Stacked::FileParms parms = {}; + parms.filename = path; + parms.info = &info; + + auto status = manager->Do(Filesystem::Call::Exists, &parms); + + return info.attributes & BC_FILE_ATTRIBUTE_DIRECTORY; +} + +// Delete a file. +bool Delete(const char* path) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.filename = path; + + auto status = manager->Do(Filesystem::Call::Delete, &parms); + return status; +} + +bool IsAbsolutePath(const char* path) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.filename = path; + return manager->Do(Filesystem::Call::IsAbsolutePath, &parms); +} + +// Get file information about path, writing information to the passed info pointer. +bool GetFileInfo(const char* path, FileInfo* info) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.filename = path; + parms.info = info; + + auto status = manager->Do(Filesystem::Call::GetFileInfo, &parms); + + return status; +} + +// Get file information from a StreamRecord, writing information to the passed info pointer. +bool GetFileInfo(StreamRecord* stream, FileInfo* info) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.stream = stream; + parms.info = info; + + return manager->Do(Filesystem::Call::GetFileInfo, &parms); +} + +bool GetWorkingDirectory(char* path, size_t capacity) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.directory = path; + parms.directorySize = capacity; + + return manager->Do(Filesystem::Call::GetWorkingDirectory, &parms); +} + +// Get file information from a stream record, returning a file info pointer owned by StreamRecord +// The FileInfo ptr returned is invalidated after a call to File::Close(stream) +FileInfo* GetFileInfo(StreamRecord* stream) { + static FileInfo s_noinfo = {}; + + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return &s_noinfo; + } + + System_File::Stacked::FileParms parms = {}; + parms.stream = stream; + parms.info = nullptr; + + auto status = manager->Do(Filesystem::Call::GetFileInfo, &parms); + + if (status) { + return parms.info; + } + + return &s_noinfo; +} + +bool MakeAbsolutePath(const char* rel, char* result, int32_t capacity, bool unkflag) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.filename = rel; + parms.flag = unkflag; + parms.directory = result; + parms.directorySize = capacity; + + return manager->Do(Filesystem::Call::MakeAbsolutePath, &parms); +} + +// Open a filename according to flags (see Defines.hpp) +// if successful, will return true and referenced file object will be set +bool Open(const char* filename, uint32_t flags, StreamRecord*& file) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.filename = filename; + parms.flag = flags; + + auto status = manager->Do(Filesystem::Call::Open, &parms); + + if (status) { + file = parms.stream; + } + + return status; +} + +bool SetAttributes(const char* filename, uint32_t attributes) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.filename = filename; + parms.flag = static_cast(attributes); + parms.mode = File::Mode::setperms; + + return manager->Do(Filesystem::Call::SetAttributes, &parms); +} + +bool Close(StreamRecord* stream) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.stream = stream; + + auto status = manager->Do(Filesystem::Call::Close, &parms); + return status; +} + +bool Copy(const char* source, const char* destination, bool overwrite) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + parms.filename = source; + parms.destination = destination; + + return manager->Do(Filesystem::Call::Copy, &parms); +} + +bool ProcessDirFast(const char* path, void* param, ProcessDirCallback callback, bool unkflag) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.filename = path; + parms.param = param; + parms.flag = unkflag; + parms.callback = callback; + + return manager->Do(Filesystem::Call::ProcessDirFast, &parms); +} + +bool Read(StreamRecord* file, void* buffer, size_t bytesToRead, size_t* bytesRead) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.stream = file; + parms.param = buffer; + parms.size = bytesToRead; + + auto status = manager->Do(Filesystem::Call::Read, &parms); + + if (bytesRead) { + *bytesRead = static_cast(parms.size); + } + + return status; +} + +bool Write(StreamRecord* file, void* buffer, size_t bytesToWrite, size_t* bytesWritten) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.stream = file; + parms.param = buffer; + parms.size = bytesToWrite; + + auto status = manager->Do(Filesystem::Call::Write, &parms); + + if (bytesWritten) { + *bytesWritten = static_cast(parms.size); + } + + return status; +} + +bool SetPos(StreamRecord* file, int64_t pos, int32_t whence) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.stream = file; + parms.position = pos; + parms.whence = whence; + + return manager->Do(Filesystem::Call::SetPos, &parms); +} + +bool GetPos(StreamRecord* file, int64_t& pos) { + auto manager = System_File::Stacked::Manager(); + if (!manager) { + return false; + } + + System_File::Stacked::FileParms parms = {}; + + parms.stream = file; + parms.position = pos; + + auto status = manager->Do(Filesystem::Call::GetPos, &parms); + if (status) { + pos = parms.position; + } + return status; +} + +} // namespace File +} // namespace Blizzard diff --git a/bc/file/File.hpp b/bc/file/File.hpp new file mode 100644 index 0000000..ef83e25 --- /dev/null +++ b/bc/file/File.hpp @@ -0,0 +1,54 @@ +#ifndef BC_FILE_FILE_HPP +#define BC_FILE_FILE_HPP + +#include "bc/Time.hpp" +#include "bc/file/Defines.hpp" +#include "bc/file/Types.hpp" +#include "bc/file/system/System_File.hpp" + +namespace Blizzard { +namespace File { + +// Functions +void SetLastError(int32_t errorcode); + +void AddToLastErrorStack(int32_t errorcode, const char* msg, int32_t param_3); + +bool Copy(const char* source, const char* destination, bool overwrite); + +bool Delete(const char* path); + +bool Exists(const char* path); + +bool IsFile(const char* path); + +bool IsDirectory(const char* path); + +bool IsAbsolutePath(const char* path); + +bool GetFileInfo(const char* path, FileInfo* info); +bool GetFileInfo(StreamRecord* stream, FileInfo* info); +FileInfo* GetFileInfo(StreamRecord* stream); + +bool GetWorkingDirectory(char* path, size_t capacity); + +bool MakeAbsolutePath(const char* rel, char* result, int32_t capacity, bool unkflag); + +bool Open(const char* filename, uint32_t flags, StreamRecord*& stream); + +bool Close(StreamRecord* stream); + +bool SetAttributes(const char* filename, uint32_t attributes); + +bool Read(StreamRecord* stream, void* buffer, size_t bytesToRead, size_t* bytesRead); + +bool Write(StreamRecord* stream, void* buffer, size_t bytesToWrite, size_t* bytesWritten); + +bool SetPos(StreamRecord* stream, int64_t pos, int32_t whence); + +bool GetPos(StreamRecord* stream, int64_t& pos); + +} // namespace File +} // namespace Blizzard + +#endif diff --git a/bc/file/Filesystem.cpp b/bc/file/Filesystem.cpp new file mode 100644 index 0000000..e4334ba --- /dev/null +++ b/bc/file/Filesystem.cpp @@ -0,0 +1,21 @@ +#include "bc/file/Filesystem.hpp" +#include + +namespace Blizzard { +namespace File { + +bool Filesystem::Do(Filesystem::Call fscall, System_File::Stacked::FileParms* parms) { + uint32_t callindex = static_cast(fscall); + // Mark byte offset of Call function. + // Allows the filesystem stack to resume normal activity in case of a fallback + parms->offset = offsetof(Filesystem, funcs) + (callindex * sizeof(uintptr_t)); + // Get filesystem call from array + + auto func = reinterpret_cast(this->funcs[callindex]); + // Do call + return func(this, parms); +} + +} // namespace File +} // namespace Blizzard + diff --git a/bc/file/Filesystem.hpp b/bc/file/Filesystem.hpp new file mode 100644 index 0000000..e51307a --- /dev/null +++ b/bc/file/Filesystem.hpp @@ -0,0 +1,66 @@ +#ifndef BC_FILE_FILESYSTEM_HPP +#define BC_FILE_FILESYSTEM_HPP + +#include "bc/file/system/Types.hpp" +#include + +namespace Blizzard { +namespace File { + +// Filesystem describes the layout of a virtual filesystem. +// It is linked to other Filesystems in a list structure. +class Filesystem { + public: + enum class Call : uint32_t { + SetWorkingDirectory, + Close, + Create, + GetWorkingDirectory, + ProcessDirFast, + Exists, + Flush, + GetFileInfo, + GetFreeSpace, + GetPos, + GetRootChars, + IsAbsolutePath, + IsReadOnly, + MakeAbsolutePath, + CreateDirectory, + Move, + Copy, + Open, + Read, + ReadP, + RemoveDirectory, + SetCacheMode, + SetEOF, + SetAttributes, + SetPos, + Delete, + Write, + WriteP, + Shutdown, + EndOfCalls + }; + + // I hate C++ enums + static constexpr size_t s_numCalls = static_cast(Call::EndOfCalls); + + // One signature for all filesystem calls + typedef bool (*CallFunc)(Filesystem*, System_File::Stacked::FileParms*); + + // The source filesystem (where it was copied from) + // You can tell if base == this, that it is the original stack and not part of the filesystem manager + Filesystem* base; + // The next filesystem (towards lower filesystems) + Filesystem* next; + CallFunc funcs[s_numCalls]; + + bool Do(Call call, System_File::Stacked::FileParms* parms); +}; + +} // namespace File +} // namespace Blizzard + +#endif diff --git a/bc/file/Path.cpp b/bc/file/Path.cpp new file mode 100644 index 0000000..9c1306a --- /dev/null +++ b/bc/file/Path.cpp @@ -0,0 +1,194 @@ +#include "bc/file/Path.hpp" +#include "bc/String.hpp" +#include "bc/Memory.hpp" +#include "bc/Debug.hpp" + +#include + +namespace Blizzard { +namespace File { +namespace Path { + +// Classes + +QuickNative::QuickNative(const char* path) { + this->size = 0; + this->fatpath = nullptr; + this->fastpath[0] = '\0'; + + if (!path) { + return; + } + + // Null byte + constexpr size_t reserved = 1; + + this->size = String::Length(path) + reserved; + + if (this->size < BC_FILE_MAX_PATH) { + // (fast) + MakeNativePath(path, this->fastpath, BC_FILE_MAX_PATH); + } else { + // (slow) + this->fatpath = reinterpret_cast(Memory::Allocate(this->size)); + MakeNativePath(path, this->fatpath, this->size); + } +} + +QuickNative::~QuickNative() { + if (this->fatpath != nullptr) { + Memory::Free(this->fatpath); + } +} + +const char* QuickNative::Str() { + if (this->fatpath != nullptr) { + return this->fatpath; + } + + return this->fastpath; +} + +size_t QuickNative::Size() { + return this->size; +} + +// Functions + +void ForceTrailingSeparator(char* buf, size_t bufMax, char sep) { + if (buf == nullptr) { + return; + } + + // If no separator character is provided, infer correct separator based on runes + if (sep == '\0') { + auto ptr = buf; + + char ch; + + while (true) { + ch = *ptr++; + // Make inference based on what data we have. + if (ch == '/' || ch == '\\') { + sep = ch; + break; + } + + if (ch == '\0') { + // Fail safe to the system's path separator. + sep = BC_FILE_SYSTEM_PATH_SEPARATOR; + break; + } + } + } + + // Buffer needs at least two characters. + if (bufMax < 2) { + return; + } + + // Slice off the end of the path buffer, so that any existing* trailing separator is discarded. + auto len = String::Length(buf); + char ch = '\0'; + if (len > 0) { + ch = buf[len - 1]; + } + switch (ch) { + case '/': + case '\\': + len--; + break; + default: + break; + } + + // Add (back*) trailing separator + BLIZZARD_ASSERT(len <= bufMax - 2); + buf[len] = sep; + buf[len + 1] = '\0'; // pad null +} + +// Convert path to DOS-style. +// the function will iterate through the path string, converting all occurrences of forward slashes (/) to backslashes (\) +bool MakeBackslashPath(const char* path, char* result, size_t capacity) { + size_t i = 0; + size_t c = capacity - 1; + + while (i <= c) { + if (path[i] == '\0') { + result[i] = '\0'; + return true; + } + + result[i] = path[i] == '/' ? '\\' : path[i]; + + i++; + } + + result[0] = '\0'; + return false; +} + +// Make a path string consistent before the last path separator. +bool MakeConsistentPath(const char* path, char* result, size_t capacity) { + if (!result || (capacity < 1)) { + return false; + } + + if (!path || (capacity == 1)) { + *result = '\0'; + return false; + } + + for (auto i = capacity - 1; i != -1; i--) { + auto ch = path[i]; + + switch (ch) { + case '\\': + return MakeBackslashPath(path, result, capacity); + case '/': + return MakeUnivPath(path, result, capacity); + case '\0': + default: + break; + } + } + + String::Copy(result, path, capacity); + return true; +} + +// Convert any path string into something that can be used on the current OS. +bool MakeNativePath(const char* path, char* result, size_t capacity) { +#if defined(WHOA_SYSTEM_WIN) + return MakeWindowsPath(path, result, capacity); +#else + return MakeUnivPath(path, result, capacity); +#endif +} + +// Convert a path string into something UNIX-friendly. +bool MakeUnivPath(const char* path, char* result, size_t capacity) { + size_t i = 0; // path and result index + size_t c = capacity - 1; // ceiling + + while (i <= c) { + if (path[i] == '\0') { + result[i] = '\0'; + return true; + } + + result[i] = path[i] == '\\' ? '/' : path[i]; + } + + result[0] = '\0'; + return false; +} + +bool MakeWindowsPath(const char* path, char* result, size_t capacity) { + return MakeBackslashPath(path, result, capacity); +} + +} // namespace Path +} // namespace File +} // namespace Blizzard diff --git a/bc/file/Path.hpp b/bc/file/Path.hpp new file mode 100644 index 0000000..20042a3 --- /dev/null +++ b/bc/file/Path.hpp @@ -0,0 +1,51 @@ +#ifndef BC_FILE_PATH_HPP +#define BC_FILE_PATH_HPP + +#include "bc/file/Defines.hpp" + +#include + +namespace Blizzard { +namespace File { +namespace Path { + +// Classes + +// Quick native path translation on the stack. +// Can be slow in some instances, as it resorts to heap allocation if path size >= BC_FILE_MAX_PATH +// Note: this name is an invention, however its behavior is repeated in all System_File implementations. +// So it probably did exist as an inlined class. +class QuickNative { + private: + size_t size; + // stack allocation + char fastpath[BC_FILE_MAX_PATH]; + // heap + char* fatpath; + + public: + QuickNative(const char* path); + ~QuickNative(); + const char* Str(); + size_t Size(); +}; + +// Functions + +void ForceTrailingSeparator(char* buf, size_t bufMax, char sep); + +bool MakeBackslashPath(const char* path, char* result, size_t capacity); + +bool MakeConsistentPath(const char* path, char* result, size_t capacity); + +bool MakeNativePath(const char* path, char* result, size_t capacity); + +bool MakeUnivPath(const char* path, char* result, size_t capacity); + +bool MakeWindowsPath(const char* path, char* result, size_t capacity); + +} // namespace Path +} // namespace File +} // namespace Blizzard + +#endif diff --git a/bc/file/Types.hpp b/bc/file/Types.hpp new file mode 100644 index 0000000..4b186f4 --- /dev/null +++ b/bc/file/Types.hpp @@ -0,0 +1,76 @@ +#ifndef BC_FILE_TYPES_HPP +#define BC_FILE_TYPES_HPP + +#include "bc/file/system/Defines.hpp" +#include "bc/time/Time.hpp" + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +#include + +namespace Blizzard { +namespace File { + +// Types + +// Used by SetCacheMode +enum Mode { + setperms = 4, + settimes = 16, + nocache = 64 +}; + +class FileInfo { + public: + Time::TimeRec* time; + // The device ID storing this file. + uint32_t device; + // Tell if a file is a directory + // read-only, hidden + // See file/Defines.hpp for more + uint32_t attributes; + // Size in bytes + uint64_t size; + // Note that these are Y2K time, not Unix + Time::Timestamp accessTime; + Time::Timestamp modificationTime; + Time::Timestamp attributeModificationTime; +}; + +class ProcessDirParms { + public: + const char* root = nullptr; + const char* item = nullptr; + bool itemIsDirectory = false; + void* param = nullptr; +}; + +typedef bool (*ProcessDirCallback)(const ProcessDirParms&); + +class StreamRecord { + public: + static constexpr size_t s_padPath = 80; + +#if defined(WHOA_SYSTEM_WIN) + HANDLE filehandle; +#endif +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + // File descriptor + int32_t filefd; +#endif + // Open flags + uint32_t flags; + // Determines whether info object is initialized + bool hasInfo; + FileInfo info; + // The path of the opened file. + char path[s_padPath]; + +}; + +} // namespace File +} // namespace Blizzard + +#endif diff --git a/bc/os/File.cpp b/bc/os/File.cpp new file mode 100644 index 0000000..655c6a2 --- /dev/null +++ b/bc/os/File.cpp @@ -0,0 +1,146 @@ +#include "bc/os/File.hpp" +#include "bc/file/Defines.hpp" +#include "bc/Debug.hpp" + +HOSFILE OsCreateFile(const char* fileName, uint32_t desiredAccess, uint32_t shareMode, uint32_t createDisposition, uint32_t flagsAndAttributes, uint32_t extendedFileType) { + // Ensure sanity + BLIZZARD_VALIDATE(fileName, "invalid filename", nullptr); + BLIZZARD_VALIDATE(desiredAccess != 0, "invalid desired access"); + BLIZZARD_VALIDATE(createDisposition <= OS_TRUNCATE_EXISTING, "invalid create disposition", nullptr); + + // Read/write flags + if (desiredAccess & OS_GENERIC_READ) { + flags |= BC_FILE_OPEN_READ; + } + if (desiredAccess & OS_GENERIC_WRITE) { + flags |= BC_FILE_OPEN_WRITE; + } + + // Allow other users to access the file in read and/or write mode + if (shareMode & OS_FILE_SHARE_READ) { + flags |= BC_FILE_OPEN_SHARE_READ; + } + if (shareMode & OS_FILE_SHARE_WRITE) { + flags |= BC_FILE_OPEN_SHARE_WRITE; + } + + // Convert createDisposition into BC flags + switch (createDisposition) { + case OS_CREATE_NEW: + // flags |= 0xC00; + flags |= BC_FILE_OPEN_CREATE | BC_FILE_OPEN_MUST_NOT_EXIST; + break; + case OS_CREATE_ALWAYS: + // flags |= 0x400; + flags |= BC_FILE_OPEN_CREATE; + break; + case OS_OPEN_EXISTING: + // flags |= 0x1000 + flags |= BC_FILE_OPEN_MUST_EXIST; + break; + case OS_OPEN_ALWAYS: + // flags |= 0x200; + flags |= BC_FILE_OPEN_ALWAYS; + break; + case OS_TRUNCATE_EXISTING: + // flags |= 0x100; + flags |= BC_FILE_OPEN_TRUNCATE; + break; + } + + // Open file + Blizzard::File::StreamRecord* stream; + bool success = Blizzard::File::Open(fileName, flags, &stream); + if (!success) { + return nullptr; + } + + // Set attributes + OsSetFileAttributes(fileName, flagsAndAttributes); + return stream; +} + +int32_t OsSetFileAttributes(const char* fileName, uint32_t attributes) { + BLIZZARD_ASSERT(fileName); + + // Translate OS file attribute bits into BlizzardCore attribute bits. + uint32_t flags = 0; + + if (attributes & OS_FILE_ATTRIBUTE_READONLY) { + // flags |= 1; + flags |= BC_FILE_ATTRIBUTE_READONLY; + } + if (attributes & OS_FILE_ATTRIBUTE_HIDDEN) { + // flags |= 2; + flags |= BC_FILE_ATTRIBUTE_HIDDEN; + } + if (attributes & OS_FILE_ATTRIBUTE_SYSTEM) { + // flags |= 4 + flags |= BC_FILE_ATTRIBUTE_SYSTEM; + } + if (attributes & OS_FILE_ATTRIBUTE_ARCHIVE) { + // flags |= 8; + flags |= BC_FILE_ATTRIBUTE_ARCHIVE; + } + if (attributes & OS_FILE_ATTRIBUTE_TEMPORARY) { + // flags |= 0x10; + flags |= BC_FILE_ATTRIBUTE_TEMPORARY; + } + if (attributes & OS_FILE_ATTRIBUTE_NORMAL) { + // flags |= 0x20; + flags |= BC_FILE_ATTRIBUTE_NORMAL; + } + if (attributes & OS_FILE_ATTRIBUTE_DIRECTORY) { + // flags |= 0x40; + flags |= BC_FILE_ATTRIBUTE_DIRECTORY; + } + + return Blizzard::File::SetAttributes(fileName, flags); +} + +uint64_t OsGetFileSize(HOSFILE fileHandle) { + auto info = Blizzard::File::GetInfo(reinterpret_cast(fileHandle)); + return info->size; +} + +int32_t OsReadFile(HOSFILE fileHandle, void* buffer, size_t bytesToRead, size_t* bytesRead) { + BLIZZARD_ASSERT(buffer); + BLIZZARD_ASSERT(bytesToRead); + + return Blizzard::File::Read(fileHandle, buffer, bytesToRead, bytesRead); +} + +int32_t OsWriteFile(HOSFILE fileHandle, void* buffer, size_t bytesToWrite, size_t* bytesWritten) { + if (buffer != nullptr && bytesToWrite != 0) { + return Blizzard::File::Write(fileHandle, buffer, bytesToWrite, bytesWritten); + } + + return 0; +} + + +int64_t OsSetFilePointer(HOSFILE fileHandle, int64_t distanceToMove, uint32_t moveMethod) { + BLIZZARD_ASSERT(moveMethod <= BC_FILE_SEEK_END); + + int64_t position; + + if (moveMethod != 0 && !Blizzard::File::GetPos(fileHandle, position)) { + return -1; + } + + uint32_t seeks[3] = { + BC_FILE_SEEK_START, + BC_FILE_SEEK_CURRENT, + BC_FILE_SEEK_END + }; + + if (!Blizzard::File::SetPos(fileHandle, position, seeks[moveMethod])) { + return -1; + } + + return position + distanceToMove; +} + +void OsCloseFile(HOSFILE fileHandle) { + Blizzard::File::Close(static_cast(fileHandle)); +} diff --git a/bc/os/File.hpp b/bc/os/File.hpp new file mode 100644 index 0000000..150a357 --- /dev/null +++ b/bc/os/File.hpp @@ -0,0 +1,65 @@ +#ifndef BC_OS_FILE_HPP +#define BC_OS_FILE_HPP + +#include "bc/file/Types.hpp" + +#include + +enum EOSFileDisposition { + OS_CREATE_NEW = 1, + OS_CREATE_ALWAYS = 2, + OS_OPEN_EXISTING = 3, + OS_OPEN_ALWAYS = 4, + OS_TRUNCATE_EXISTING = 5 +}; + +enum EOSFileAccess { + OS_GENERIC_ALL = 0x10000000, + OS_GENERIC_EXECUTE = 0x20000000, + OS_GENERIC_WRITE = 0x40000000, + OS_GENERIC_READ = 0x80000000 +}; + +enum EOSFileShare { + OS_FILE_SHARE_READ = 0x00000001, + OS_FILE_SHARE_WRITE = 0x00000002 +}; + +enum EOSFileFlagsAndAttributes { + OS_FILE_ATTRIBUTE_READONLY = 0x1, + OS_FILE_ATTRIBUTE_HIDDEN = 0x2, + OS_FILE_ATTRIBUTE_SYSTEM = 0x4, + OS_FILE_ATTRIBUTE_DIRECTORY = 0x10, + OS_FILE_ATTRIBUTE_ARCHIVE = 0x20, + OS_FILE_ATTRIBUTE_NORMAL = 0x80, + OS_FILE_ATTRIBUTE_TEMPORARY = 0x100, + OS_FILE_ATTRIBUTE_OFFLINE = 0x1000, + OS_FILE_ATTRIBUTE_ENCRYPTED = 0x4000, + + OS_FILE_FLAG_OPEN_NO_RECALL = 0x00100000, + OS_FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000, + OS_FILE_FLAG_POSIX_SEMANTICS = 0x01000000, + OS_FILE_FLAG_BACKUP_SEMANTICS = 0x02000000, + OS_FILE_FLAG_DELETE_ON_CLOSE = 0x04000000, + OS_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000, + OS_FILE_FLAG_RANDOM_ACCESS = 0x10000000, + OS_FILE_FLAG_NO_BUFFERING = 0x20000000, + OS_FILE_FLAG_OVERLAPPED = 0x40000000, + OS_FILE_FLAG_WRITE_THROUGH = 0x80000000 +}; + +typedef Blizzard::File::StreamRecord* HOSFILE; + +HOSFILE OsCreateFile(const char* fileName, uint32_t desiredAccess, uint32_t shareMode, uint32_t createDisposition, uint32_t flagsAndAttributes, uint32_t extendedFileType); + +int32_t OsSetFileAttributes(const char* fileName, uint32_t attributes); + +uint64_t OsGetFileSize(HOSFILE fileHandle); + +int32_t OsReadFile(HOSFILE fileHandle, void* buffer, uint32_t bytesToRead, uint32_t* bytesRead); + +void OsCloseFile(HOSFILE fileHandle); + +int64_t OsSetFilePointer(HOSFILE fileHandle, int64_t distanceToMove, uint32_t moveMethod); + +#endif diff --git a/bc/system/file/Defines.hpp b/bc/system/file/Defines.hpp new file mode 100644 index 0000000..80116b9 --- /dev/null +++ b/bc/system/file/Defines.hpp @@ -0,0 +1,13 @@ +#ifndef BC_FILE_SYSTEM_DEFINES_HPP +#define BC_FILE_SYSTEM_DEFINES_HPP + +// Constants + +// How much data to buffer when copying with File::Copy +#define BC_FILE_SYSTEM_COPYBUFFER_SIZE 0xA00000UL + +// Utility macros + +#define BC_FILE_PATH(localname) char localname[BC_FILE_MAX_PATH] = {0} + +#endif diff --git a/bc/system/file/Stacked.cpp b/bc/system/file/Stacked.cpp new file mode 100644 index 0000000..397890a --- /dev/null +++ b/bc/system/file/Stacked.cpp @@ -0,0 +1,291 @@ + +#include "bc/file/Filesystem.hpp" +#include "bc/file/system/System_File.hpp" +#include "bc/file/system/Stacked.hpp" +#include "bc/String.hpp" +#include "bc/Memory.hpp" + +namespace Blizzard { +namespace System_File { +namespace Stacked { + +bool null_cd(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_close(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_create(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_cwd(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_dirwalk(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_exists(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_flush(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_getfileinfo(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_getfreespace(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_getpos(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_getrootchars(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_isabspath(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_isreadonly(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_makeabspath(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_mkdir(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_move(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_copy(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_open(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_read(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_readp(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_rmdir(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_setcachemode(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_seteof(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_setfileinfo(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_setpos(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_unlink(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_write(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_writep(File::Filesystem* fs, FileParms* parms) { + return false; +} +bool null_shutdown(File::Filesystem* fs, FileParms* parms) { + return false; +} + +// Null stack: this stack does absolutely nothing. Its only purpose is to store fallback functions. +static File::Filesystem s_nullstack = { + &s_nullstack, + + nullptr, + + { + null_cd, + null_close, + null_create, + null_cwd, + null_dirwalk, + null_exists, + null_flush, + null_getfileinfo, + null_getfreespace, + null_getpos, + null_getrootchars, + null_isabspath, + null_isreadonly, + null_makeabspath, + null_mkdir, + null_move, + null_copy, + null_open, + null_read, + null_readp, + null_rmdir, + null_setcachemode, + null_seteof, + null_setfileinfo, + null_setpos, + null_unlink, + null_write, + null_writep, + null_shutdown + } +}; + +// Base stack: this stack contains the base System_File functions. +static File::Filesystem s_basestack = { + &s_basestack, + + nullptr, + + { + System_File::SetWorkingDirectory, + System_File::Close, + System_File::Create, + System_File::GetWorkingDirectory, + System_File::ProcessDirFast, + System_File::Exists, + System_File::Flush, + System_File::GetFileInfo, + System_File::GetFreeSpace, + System_File::GetPos, + System_File::GetRootChars, + System_File::IsAbsolutePath, + System_File::IsReadOnly, + System_File::MakeAbsolutePath, + System_File::CreateDirectory, + System_File::Move, + System_File::Copy, + System_File::Open, + System_File::Read, + System_File::ReadP, + System_File::RemoveDirectory, + System_File::SetCacheMode, + System_File::SetEOF, + System_File::SetAttributes, + System_File::SetPos, + System_File::Delete, + System_File::Write, + System_File::WriteP, + System_File::Shutdown + } +}; + +// File initialization stack: this is the default +static File::Filesystem s_fileinit = { + &s_fileinit, + + nullptr, + + { + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init, + file_init + } +}; + +// Manager will be initialized upon first use +static File::Filesystem* s_manager = &s_fileinit; + +// Manager getter +File::Filesystem* Manager() { + return s_manager; +} + +// Push a filesystem stack to manager linked list +void Push(File::Filesystem* fs) { + auto stack = reinterpret_cast(Memory::Allocate(sizeof(File::Filesystem))); + String::MemFill(stack, sizeof(File::Filesystem), 0); + + // Add stack + String::MemCopy(stack, fs, sizeof(File::Filesystem)); + stack->next = s_manager; + + s_manager = stack; + + HoistAll(); +} + +// file_init is part of the default filesystem stack +// The first use of s_manager will call this function only once; it is a fallback function in case s_basestack's funcs are not yet hoisted. +bool file_init(File::Filesystem* fs, FileParms* parms) { + // Allocate a null stack, replacing what was previously a pointer to s_fileinit + s_manager = reinterpret_cast(Memory::Allocate(sizeof(File::Filesystem))); + if (s_manager == nullptr) { + return false; + } + + // Add null fs calls to stack + // After Push/HoistAll, if a filesystem call is unimplemented, the func from s_nullstack gets called instead. + String::MemCopy(s_manager, &s_nullstack, sizeof(File::Filesystem)); + + // Hoist basic file functions + Push(&s_basestack); + + // fs manager is now ready to use! + // Execute the original call. + if (s_manager != nullptr) { + auto func = *reinterpret_cast(reinterpret_cast(s_manager) + parms->offset); + return func(fs, parms); + } + + return false; +} + +// Ensures that the filesystem manager will always have a corresponding function address for enum Call call. +void Hoist(uint32_t call) { + if (s_manager->funcs[call] == nullptr) { + auto topStack = s_manager; + auto prev = s_manager; + auto next = s_manager->next; + while (next && topStack->funcs[call] == nullptr) { + topStack->funcs[call] = next->funcs[call]; + prev = next; + next = next->next; + } + + // Remove call from lower stack + prev->funcs[call] = nullptr; + } +} + +// Hoist all functions in the filesystem stack to the top, i.e. the manager +void HoistAll() { + for (uint32_t call = 0; call < File::Filesystem::s_numCalls; call++) { + Hoist(call); + } +} + +} // namespace Stacked +} // namespace System_File +} // namespace Blizzard diff --git a/bc/system/file/Stacked.hpp b/bc/system/file/Stacked.hpp new file mode 100644 index 0000000..e56f022 --- /dev/null +++ b/bc/system/file/Stacked.hpp @@ -0,0 +1,85 @@ +#ifndef BC_FILE_SYSTEM_STACKED_HPP +#define BC_FILE_SYSTEM_STACKED_HPP + +#include "bc/file/Types.hpp" +#include "bc/file/Filesystem.hpp" +#include "bc/file/File.hpp" +#include "bc/file/system/System_File.hpp" + +#include + +namespace Blizzard { +namespace System_File { +namespace Stacked { + +// Functions +bool Open(FileParms* parms); + +bool file_init(File::Filesystem* fs, FileParms* parms); + +File::Filesystem* Manager(); + +void HoistAll(); + +void Push(File::Filesystem* fs); + +// Stacked file functions +bool SetWorkingDirectory(FileParms* parms); + +bool Close(FileParms* parms); + +bool GetWorkingDirectory(FileParms* parms); + +bool ProcessDirFast(FileParms* parms); + +bool Exists(FileParms* parms); + +bool Flush(FileParms* parms); + +bool GetFileInfo(FileParms* parms); + +bool GetFreeSpace(FileParms* parms); + +bool GetPos(FileParms* parms); + +bool GetRootChars(FileParms* parms); + +bool IsAbsolutePath(FileParms* parms); + +bool IsReadOnly(FileParms* parms); + +bool MakeAbsolutePath(FileParms* parms); + +bool CreateDirectory(FileParms* parms); + +bool Move(FileParms* parms); + +bool Copy(FileParms* parms); + +bool Open(FileParms* parms); + +bool Read(FileParms* parms); + +bool ReadP(FileParms* parms); + +bool RemoveDirectory(FileParms* parms); + +bool SetCacheMode(FileParms* parms); + +bool SetEOF(FileParms* parms); + +bool SetAttributes(FileParms* parms); + +bool SetPos(FileParms* parms); + +bool Delete(FileParms* parms); + +bool Write(FileParms* parms); + +bool WriteP(FileParms* parms); + +} // namespace Stacked +} // namespace System_File +} // namespace Blizzard + +#endif diff --git a/bc/system/file/System_File.hpp b/bc/system/file/System_File.hpp new file mode 100644 index 0000000..7dbe55a --- /dev/null +++ b/bc/system/file/System_File.hpp @@ -0,0 +1,51 @@ +#ifndef BC_FILE_SYSTEM_SYSTEM_FILE_HPP +#define BC_FILE_SYSTEM_SYSTEM_FILE_HPP + +#include "bc/Debug.hpp" +#include "bc/file/system/Types.hpp" +#include "bc/file/Filesystem.hpp" + +#if defined(WHOA_SYSTEM_WIN) +#include "bc/file/system/win/Support.hpp" +#endif + +#include + +namespace Blizzard { +namespace System_File { + +// Fs calls +bool SetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms); +bool Close(File::Filesystem* fs, Stacked::FileParms* parms); +bool Create(File::Filesystem* fs, Stacked::FileParms* parms); +bool GetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms); +bool ProcessDirFast(File::Filesystem* fs, Stacked::FileParms* parms); +bool Exists(File::Filesystem* fs, Stacked::FileParms* parms); +bool Flush(File::Filesystem* fs, Stacked::FileParms* parms); +bool GetFileInfo(File::Filesystem* fs, Stacked::FileParms* parms); +bool GetFreeSpace(File::Filesystem* fs, Stacked::FileParms* parms); +bool GetPos(File::Filesystem* fs, Stacked::FileParms* parms); +bool GetRootChars(File::Filesystem* fs, Stacked::FileParms* parms); +bool IsAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms); +bool IsReadOnly(File::Filesystem* fs, Stacked::FileParms* parms); +bool MakeAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms); +bool CreateDirectory(File::Filesystem* fs, Stacked::FileParms* parms); +bool Move(File::Filesystem* fs, Stacked::FileParms* parms); +bool Copy(File::Filesystem* fs, Stacked::FileParms* parms); +bool Open(File::Filesystem* fs, Stacked::FileParms* parms); +bool Read(File::Filesystem* fs, Stacked::FileParms* parms); +bool ReadP(File::Filesystem* fs, Stacked::FileParms* parms); +bool RemoveDirectory(File::Filesystem* fs, Stacked::FileParms* parms); +bool SetCacheMode(File::Filesystem* fs, Stacked::FileParms* parms); +bool SetEOF(File::Filesystem* fs, Stacked::FileParms* parms); +bool SetAttributes(File::Filesystem* fs, Stacked::FileParms* parms); +bool SetPos(File::Filesystem* fs, Stacked::FileParms* parms); +bool Delete(File::Filesystem* fs, Stacked::FileParms* parms); +bool Write(File::Filesystem* fs, Stacked::FileParms* parms); +bool WriteP(File::Filesystem* fs, Stacked::FileParms* parms); +bool Shutdown(File::Filesystem* fs, Stacked::FileParms* parms); + +} // namespace System_File +} // namespace Blizzard + +#endif diff --git a/bc/system/file/Types.hpp b/bc/system/file/Types.hpp new file mode 100644 index 0000000..f8aa1ff --- /dev/null +++ b/bc/system/file/Types.hpp @@ -0,0 +1,70 @@ +#ifndef BC_FILE_SYSTEM_TYPES_HPP +#define BC_FILE_SYSTEM_TYPES_HPP + +#include "bc/Debug.hpp" +#include "bc/file/Types.hpp" + +#include + +namespace Blizzard { + +namespace System_File { + +class FileError { + public: + // Error codes can be any value, even negative. + // Look for common error codes in Defines.hpp + // on POSIX, errorcode could be a POSIX error offset +100. + int32_t errorcode; + Debug::ErrorStackRecord* stack; +}; + +namespace Stacked { + +// FileParms stores parameters useful to all filesystem interactions +// so all fs calls can use one signature +class FileParms { + public: + // The byte offset to the filesystem action func of this call. + uintptr_t offset; + // The "source" file object + const char* filename; + // The "destination" file object, or just a user parameter. + union { + void* param; + const char* destination; + }; + // Supplies the handle/fd of a file + File::StreamRecord* stream; + // Flag or boolean field. + uint32_t flag; + // Stores additional status bits. + uint32_t mode; + File::FileInfo* info; + // Beginning and end used when querying slices: such as GetRootChars + union { + // The specified position of the interaction + int64_t position; + int64_t beginning; + }; + union { + int32_t whence; + int64_t end; + }; + union { + // size_t is not guaranteed to be 64-bit + // size64 is included here as well to allow interactions on files > 4 GB in size. + uint64_t size64; + size_t size; + }; + File::ProcessDirCallback callback; + char* directory; + size_t directorySize; +}; + +} // namespace Stacked + +} // namespace System_File +} // namespace Blizzard + +#endif diff --git a/bc/system/file/posix/Stacked.cpp b/bc/system/file/posix/Stacked.cpp new file mode 100644 index 0000000..9826dfc --- /dev/null +++ b/bc/system/file/posix/Stacked.cpp @@ -0,0 +1,602 @@ +#include "bc/file/system/Stacked.hpp" +#include "bc/file/Path.hpp" +#include "bc/Debug.hpp" +#include "bc/Memory.hpp" +#include "bc/String.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Blizzard { +namespace System_File { +namespace Stacked { + +bool Open(FileParms* parms) { + File::Path::QuickNative pathNative(parms->filename); + + // Build POSIX flags based on flags from FileParms + bool read = parms->flag & BC_FILE_OPEN_READ; + bool write = parms->flag & BC_FILE_OPEN_WRITE; + bool mustNotExist = parms->flag & BC_FILE_OPEN_MUST_NOT_EXIST; + bool mustExist = parms->flag & BC_FILE_OPEN_MUST_EXIST; + bool create = parms->flag & BC_FILE_OPEN_CREATE; + bool truncate = parms->flag & BC_FILE_OPEN_TRUNCATE; + + BLIZZARD_ASSERT(read || write); + + int32_t flags = 0; + + if (read && !write) { + flags = O_RDONLY; + } else if (!read && write) { + flags = O_WRONLY; + } else if (read && write) { + flags = O_RDWR; + } + + int32_t fd = -1; + + if (create) { + flags |= O_CREAT; + + if (mustNotExist) { + flags |= O_EXCL; + } + + fd = ::open(pathNative.Str(), flags, 511); + } else { + fd = ::open(pathNative.Str(), flags); + } + + if (fd == -1) { + BC_FILE_SET_ERROR_MSG(100 + errno, "Posix 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); + if (fileData == nullptr) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_OOM); + return false; + } + + String::MemFill(fileData, recordSize, 0); + + auto file = reinterpret_cast(fileData); + + file->flags = flags; + file->filefd = fd; + + String::Copy(file->path, parms->filename, pathNative.Size()); + + File::GetFileInfo(file); + + parms->stream = file; + + return true; +} + +bool Exists(FileParms* parms) { + BC_FILE_PATH(buffer); + + auto filepath = parms->filename; + auto empty = ""; + size_t len = 0; + struct stat info = {}; + bool exists = false; + + File::Path::QuickNative filepathNative(filepath); + + auto status = ::stat(filepathNative.Str(), &info); + + if (status != -1) { + parms->info->attributes = 0; + // Collect attributes. + if (S_ISDIR(info.st_mode)) { + parms->info->attributes |= BC_FILE_ATTRIBUTE_DIRECTORY; + } + + if (S_ISREG(info.st_mode)) { + exists = true; + parms->info->attributes |= BC_FILE_ATTRIBUTE_NORMAL; + } + } + + return exists; +} + +bool GetFreeSpace(FileParms* parms) { + auto dirpath = parms->filename; + + if (dirpath == nullptr || *dirpath == '\0') { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + File::Path::QuickNative dirpathNative(dirpath); + + struct statvfs sv; + if (::statvfs(dirpathNative.Str(), &sv) != 0) { + BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_INVALID_ARGUMENT, "Posix GetFreeSpace - %s", dirpath); + return false; + } + + parms->size64 = sv.f_bavail * sv.f_frsize; + return true; +} + +bool ProcessDirFast(FileParms* parms) { + auto dirpath = parms->filename; + File::Path::QuickNative dirpathNative(dirpath); + + // Call back to this function when processing + File::ProcessDirCallback callback = parms->callback; + + // Open directory + auto directory = ::opendir(dirpathNative.Str()); + if (!directory) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + // Stores names temporarily + char name[256] = {0}; + + bool status = false; + + File::ProcessDirParms walkparms = {}; + walkparms.root = parms->filename; + walkparms.param = parms->param; + walkparms.item = name; + + struct dirent* ent = nullptr; + + while ((ent = ::readdir(directory)) != nullptr) { + String::Copy(name, ent->d_name, 256); + + auto isDotCurrent = (name[0] == '.' && name[1] == '\0'); + auto isDotParent = (name[0] == '.' && name[1] == '.' && name[2] == '\0'); + + if (!(isDotCurrent || isDotParent)) { + walkparms.itemIsDirectory = ent->d_type == DT_DIR; + status = callback(walkparms); + if (status) { + break; + } + } + } + + ::closedir(directory); + return status; +} + +bool IsReadOnly(FileParms* parms) { + auto manager = Manager(); + auto filename = parms->filename; + + if (!filename || !manager || !File::Exists(filename)) { + // FileError(8) + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + File::StreamRecord* stream = nullptr; + + auto flags = BC_FILE_OPEN_READ | BC_FILE_OPEN_WRITE | BC_FILE_OPEN_MUST_EXIST; + + bool opened = File::Open(filename, flags, stream); + if (!opened) { + return true; + } + + File::Close(stream); + return false; +} + +// realpath() based +bool MakeAbsolutePath(FileParms* parms) { + auto unkflag = parms->flag; + + BC_FILE_PATH(basepathfast); + BC_FILE_PATH(univpathfast); + char* basepath = nullptr; + char* univpath = nullptr; + + if (parms->directorySize < BC_FILE_MAX_PATH) { + basepath = basepathfast; + } else { + basepath = reinterpret_cast(Memory::Allocate(parms->directorySize)); + } + + if (!File::IsAbsolutePath(parms->filename)) { + // If the path is not absolute already, pack current working dir to the base path. + File::GetWorkingDirectory(basepath, parms->directorySize); + + // Force a slash to the end of the base path, so that we can append univpath + File::Path::ForceTrailingSeparator(basepath, parms->directorySize, BC_FILE_SYSTEM_PATH_SEPARATOR); + } + + String::Append(basepath, parms->filename, parms->directorySize); + + if (parms->directorySize <= BC_FILE_MAX_PATH) { + univpath = univpathfast; + } else { + univpath = reinterpret_cast(Memory::Allocate(parms->directorySize)); + } + + File::Path::MakeNativePath(basepath, univpath, parms->directorySize); + + const char* local_1060 = nullptr; + const char* local_1054 = nullptr; + char* temp_directory = nullptr; + size_t temp_size = 0; + char* copied_univ_path = nullptr; + char* previous_character = nullptr; + char* next_slash = nullptr; + BC_FILE_PATH(next_slash_fast); + BC_FILE_PATH(temp_directory_fast); + char current_byte = 0; + size_t len = 0; + size_t copied_len = 0; + + auto after_first_slash = univpath + 1; + loop_start: + do { + next_slash = String::Find(after_first_slash, '/', parms->directorySize); + if (next_slash == nullptr) { + len = String::Length(univpath); + if (((len > 2) && (univpath[len - 1] == '.')) && (univpath[len - 2] == '/')) { + univpath[len - 1] = '\0'; + } + + if (unkflag) { + temp_size = parms->directorySize; + + if (temp_size <= BC_FILE_MAX_PATH) { + temp_directory = temp_directory_fast; + } else { + temp_directory = reinterpret_cast(Memory::Allocate(temp_size)); + } + + local_1060 = temp_directory; + local_1054 = univpath; + + after_first_slash = univpath; + copied_univ_path = temp_directory; + eat_byte: + current_byte = *after_first_slash; + if (current_byte != '\0') { + current_byte_must_be_forward_slash: + if (current_byte != '/') { + break; + } + goto do_workingdir_buffer_realpath; + } + + copy_universalpath: + String::Copy(univpath, copied_univ_path, parms->directorySize); + if ((copied_univ_path != local_1060) && (copied_univ_path != nullptr)) { + Memory::Free(copied_univ_path); + } + } + + String::Copy(parms->directory, univpath, parms->directorySize); + current_byte = *parms->directory; + + if (basepath != basepathfast && basepath != nullptr) { + // 0x1a211d + Memory::Free(basepath); + } + if (univpath != univpathfast && univpath != nullptr) { + // 0x1a2137 + Memory::Free(univpath); + } + + return current_byte != '\0'; + } + + if (next_slash[1] != '.') { + copy_or_increment_resume_loop: + if ((univpath < previous_character) && (after_first_slash == next_slash)) { + String::Copy(next_slash,next_slash + 1,parms->directorySize); + } else { + after_first_slash = next_slash + 1; + previous_character = next_slash; + } + goto loop_start; + } + + if (next_slash[2] == '.') { + if (next_slash[3] != '/') { + goto copy_or_increment_resume_loop; + } + String::Copy(after_first_slash, next_slash + 4, parms->directorySize); + } else { + if (next_slash[2] != '/') { + goto copy_or_increment_resume_loop; + } + String::Copy(next_slash + 1, next_slash + 3, parms->directorySize); + } + } while(true); + + after_first_slash = after_first_slash + 1; + current_byte = *after_first_slash; + + if (current_byte == '\0') { + do_workingdir_buffer_realpath: + previous_character = after_first_slash + 1; + String::Copy(temp_directory, local_1054, (static_cast(previous_character - local_1054)) + 1); + if (parms->directorySize <= BC_FILE_MAX_PATH) { + next_slash = next_slash_fast; + } else { + next_slash = reinterpret_cast(Memory::Allocate(parms->directorySize)); + } + + + if (::realpath(copied_univ_path, next_slash) == nullptr) { + temp_directory = temp_directory + (static_cast(previous_character - local_1054)); + } else { + String::Copy(copied_univ_path,next_slash,parms->directorySize); + if ((*after_first_slash == '/') || ((*after_first_slash == '\0' && (after_first_slash[-1] == '/')))) { + File::Path::ForceTrailingSeparator(copied_univ_path, parms->directorySize, '/'); + } + temp_directory = copied_univ_path; + copied_len = String::Length(copied_univ_path); + temp_directory = temp_directory + copied_len; + } + + if (*after_first_slash != '\0') { + after_first_slash = previous_character; + local_1054 = previous_character; + } + + if ((next_slash == next_slash_fast) || (next_slash == nullptr)) { + goto eat_byte; + } + + Memory::Free(next_slash); + current_byte = *after_first_slash; + if (current_byte == '\0') { + goto copy_universalpath; + } + } + goto current_byte_must_be_forward_slash; +} + +// Create a full directory path +bool CreateDirectory(FileParms* parms) { + if (parms->filename == nullptr) { + // FileError(8) + BC_FILE_SET_ERROR(8); + return false; + } + + char tmp[BC_FILE_MAX_PATH] = {}; + struct stat sb; + + // Copy path + File::Path::MakeNativePath(parms->filename, tmp, BC_FILE_MAX_PATH); + + auto len = String::Length(tmp); + + // Remove trailing slash + if(tmp[len - 1] == '/') { + tmp[len - 1] = '\0'; + } + + // check if path exists and is a directory + if (::stat(tmp, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) { + return true; + } + } + + // Loop through path and call mkdir on path elements + for (auto p = tmp + 1; *p != '\0'; p++) { + if (*p == '/') { + *p = 0; + // test path + if (::stat(tmp, &sb) != 0) { + // path does not exist, create directory + if (::mkdir(tmp, 511) < 0) { + return false; + } + } else if (!S_ISDIR(sb.st_mode)) { + // not a directory + return false; + } + *p = '/'; + } + } + + // check remaining path existence + if (::stat(tmp, &sb) != 0) { + // path does not exist, create directory + if (::mkdir(tmp, 511) < 0) { + return false; + } + } else if (!S_ISDIR(sb.st_mode)) { + // not a directory + return false; + } + + return true; +} + +bool Move(FileParms* parms) { + File::Path::QuickNative source(parms->filename); + File::Path::QuickNative destination(parms->destination); + + struct stat st; + + // Fail if destination already exists. + int32_t status = ::stat(destination.Str(), &st); + if (status == 0) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + // See if we can just rename the file. Pretty fast if we can avoid copying. + status = ::rename(source.Str(), destination.Str()); + if (status == 0) { + return true; + } + + // If rename failed due to cross-device linking (can't rename to a different device) + // Copy the file from one device to another (slow) + if (errno == EXDEV) { + if (File::Copy(parms->filename, parms->destination, false)) { + // Source is deleted once File::Copy is successful. + File::Delete(parms->filename); + return true; + } + return true; + } + + return false; +} + +// Attempts to remove an empty directory. +bool RemoveDirectory(FileParms* parms) { + auto dir = parms->filename; + + // Must have directory path to remove directory. + if (!dir) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + // Convert to native path. + File::Path::QuickNative dirNative(dir); + + // Attempt rmdir + return ::rmdir(dirNative.Str()) == 0; +} + +// Change file EOF to a new offset @ parms->position and according to parms->whence. +// DESTROYING any existing data at offset >= parms->position +bool SetEOF(FileParms* parms) { + auto file = parms->stream; + + // Seek to truncation point based on position and whence + // then, read computed truncation point into parms->position + if (!File::SetPos(file, parms->position, parms->whence) || + !File::GetPos(file, parms->position)) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + // Perform truncation. + auto status = ::ftruncate(file->filefd, static_cast(parms->position)); + if (status != -1) { + // Success! + + // presumably this is to invalidate stream's fileinfo (which is no longer recent) + file->hasInfo = false; + return true; + } + + // Some POSIX error has occurred. + + // Can occur as user may have tried to to resize to a large parms->position + if (errno == ENOSPC) { + // Code(9) + BC_FILE_SET_ERROR(BC_FILE_ERROR_NO_SPACE_ON_DEVICE); + return false; + } + + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; +} + +// Changes file attributes of object at parms->filename +// Sets filemode with chmod() +// Changes update times with futimes() +bool SetAttributes(FileParms* parms) { + BC_FILE_PATH(path); + + auto info = parms->info; + + if (info == nullptr) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + uint32_t mode = parms->mode; + auto attributes = info->attributes; + int32_t status = 0; + auto file = parms->stream; + + if (mode & File::Mode::settimes) { +#if defined(WHOA_SYSTEM_MAC) + // Use BSD function futimes + struct timeval tvs[2]; + + tvs[0].tv_sec = Time::ToUnixTime(info->modificationTime); + tvs[0].tv_usec = 0; + + tvs[1].tv_sec = tvs[0].tv_sec; + tvs[1].tv_usec = 0; + + // Attempt to apply times to file descriptor. + status = ::futimes(file->filefd, tvs); +#else + // use Linux equivalent futimens + struct timespec tsp[2]; + tsp[0].tv_sec = Time::ToUnixTime(info->modificationTime); + tsp[0].tv_nsec = 0; + + tsp[1].tv_sec = tsp[0].tv_sec; + tsp[1].tv_nsec = 0; + status = ::futimens(file->filefd, tsp); +#endif + + if (status != 0) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + // If successful, also apply these changes to FileInfo inside StreamRecord. + file->info.accessTime = info->modificationTime; + file->info.modificationTime = info->modificationTime; + parms->mode &= ~(File::Mode::settimes); + } + + if (mode & File::Mode::setperms) { + File::Path::QuickNative path(parms->filename); + + // Get unix permissions + struct stat info = {}; + auto status = ::stat(path.Str(), &info); + if (status == -1) { + // Can't set attributes on a nonexistent file ૮ ・ﻌ・ა + return false; + } + + if (attributes & BC_FILE_ATTRIBUTE_READONLY) { + status = ::chmod(path.Str(), 444); + } else { + status = ::chmod(path.Str(), 511); + } + + if (status != 0) { + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + parms->mode &= ~(File::Mode::setperms); + } + + return true; +} + +} // namespace Stacked +} // namespace System_File +} // namespace Blizzard diff --git a/bc/system/file/posix/System_File.cpp b/bc/system/file/posix/System_File.cpp new file mode 100644 index 0000000..5402c80 --- /dev/null +++ b/bc/system/file/posix/System_File.cpp @@ -0,0 +1,512 @@ +#include "bc/file/system/System_File.hpp" +#include "bc/file/system/Stacked.hpp" +#include "bc/file/Path.hpp" +#include "bc/String.hpp" +#include "bc/Debug.hpp" +#include "bc/Memory.hpp" + +#include +#include +#include +#include + +namespace Blizzard { +namespace System_File { + +/***************************************************** +* Begin POSIX-compatible System_File stack functions * +******************************************************/ + +// Change current process's working directory to parms->filename. +bool SetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + BLIZZARD_ASSERT(parms->filename); + + auto len = Blizzard::String::Length(parms->filename) + 1; + char wd[BC_FILE_MAX_PATH] = {0}; + File::Path::MakeNativePath(parms->filename, wd, len); + + return chdir(wd) == 0; +} + +// Close parms->stream file descriptor. +bool Close(File::Filesystem* fs, Stacked::FileParms* parms) { + auto file = parms->stream; + BLIZZARD_ASSERT(file != nullptr); + + close(file->filefd); + Memory::Free(file); + + return true; +} + +// Maybe unimplemented because Open already implements a creation flag +// so Create is unecessary? +bool Create(File::Filesystem* fs, Stacked::FileParms* parms) { + // System_File::FileError(9) + BC_FILE_SET_ERROR(BC_FILE_ERROR_UNIMPLEMENTED); + + return false; +} + +// Get the process's working directory to parms->directory. +// parms->directory comes with a cap specified by parms->directorySize. +bool GetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + if (parms->directory && parms->directorySize > 0) { + *parms->directory = '\0'; + + return getcwd(parms->directory, parms->directorySize) != 0; + } + + // System_File::FileError(8) + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; +} + +// iterate through directory calling back to parms->callback +bool ProcessDirFast(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::ProcessDirFast(parms); +} + +// Boolean return here is simply whether a file (not a folder) Exists +bool Exists(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Exists(parms); +} + +// Causes all modified data and attributes of file descriptor to be moved to +// a permanent storage device. This normally results in all in-core modified +// copies of buffers for the associated file to be written to a disk. +bool Flush(File::Filesystem* fs, Stacked::FileParms* parms) { + auto file = parms->stream; + BLIZZARD_ASSERT(file != nullptr); + +#if defined(WHOA_SYSTEM_MAC) + // from BSD syscall manual: + // "Note that while fsync() will flush all data from the host to the drive + // (i.e. the "permanent storage device"), the drive itself may not physi- + // cally write the data to the platters for quite some time and it may be + // written in an out-of-order sequence." + + // "The F_FULLFSYNC fcntl asks the drive to flush all buffered data to permanent storage." + auto status = fcntl(file->filefd, F_FULLFSYNC, 0); + + if (status == 0) { + return true; + } +#endif + // Attempts to transfer all writes in the cache buffer to the underlying storage device. + // fsync() does not return until the transfer is complete, or until an error is detected. + return fsync(file->filefd) == 0; +} + +// Attempt to request information about a filepath or an opened StreamRecord +bool GetFileInfo(File::Filesystem* fs, Stacked::FileParms* parms) { + // Set up arguments + auto filename = parms->filename; + struct stat info; + int32_t status = -1; + File::FileInfo* infoptr = parms->info; + + // Instead of a filename, a file descriptor can be supplied. + if (filename == nullptr && parms->stream != nullptr) { + // Info may be available from the file descriptor + auto file = parms->stream; + + // Read stat from from stream fd if it exists + if (file) { + status = fstat(file->filefd, &info); + } + + // If an info struct was not supplied, + // Allow user to request file info pointer stored in StreamRecord + if (infoptr == nullptr) { + infoptr = &file->info; + parms->info = infoptr; + } + } else { + // Convert filename to native style. + File::Path::QuickNative path(filename); + // read stat from converted path + status = stat(path.Str(), &info); + } + + // Set existence bool + auto exists = status != -1; + + // Collect POSIX filesystem info into File::FileInfo structure + if (exists) { + // Collect attributes. + if (S_ISDIR(info.st_mode)) { + infoptr->attributes |= BC_FILE_ATTRIBUTE_DIRECTORY; + } + + if (S_ISREG(info.st_mode)) { + infoptr->attributes |= BC_FILE_ATTRIBUTE_NORMAL; + } + + mode_t readonly = 0444; + + if ((info.st_mode & readonly) == readonly) { + infoptr->attributes |= BC_FILE_ATTRIBUTE_READONLY; + } + + infoptr->device = static_cast(info.st_dev); + infoptr->size = static_cast(info.st_size); + + infoptr->accessTime = Time::FromUnixTime(info.st_atime); + infoptr->modificationTime = Time::FromUnixTime(info.st_mtime); + infoptr->attributeModificationTime = Time::FromUnixTime(info.st_ctime); + } + + return true; +} + +bool GetFreeSpace(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetFreeSpace(parms); +} + +// Get current read/write position @parms->position +bool GetPos(File::Filesystem* fs, Stacked::FileParms* parms) { + auto cur = lseek(parms->stream->filefd, 0, SEEK_CUR); + if (cur == -1) { + // FileError(8) + BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT); + return false; + } + + parms->position = static_cast(cur); + + return true; +} + +// Return an index @parms->position, pointing to the end of the "root chars" portion of a string. +// On MacOS, this would be /Volumes/ +bool GetRootChars(File::Filesystem* fs, Stacked::FileParms* parms) { + auto name = parms->filename; + + if (*name == '/' || *name == '\\') { + parms->position = 1; + +#if defined(WHOA_SYSTEM_MAC) + if (name != -1) { + auto cmp = strncasecmp(name + 1, "Volumes", 7); + auto volume = name + 9; + + auto ch = *volume; + + while(ch != '\0' && ch != '/' && ch != '\\') { + ch = *volume++; + } + + parms->position = reinterpret_cast(volume) - reinterpret_cast(name); + + } +#endif + } + + return true; +} + +// Returns true if parms->filename is not a relative path. +bool IsAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms) { + return *parms->filename == '/' || *parms->filename == '\\'; +} + +// Return true if @parms->filename is readonly. +bool IsReadOnly(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::IsReadOnly(parms); +} + +// Take source parms->filename path and prefix it with containing path. +bool MakeAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::MakeAbsolutePath(parms); +} + +// Create directory named parms->filename +bool CreateDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::CreateDirectory(parms); +} + +// Move a file from parms->filename to parms->destination +bool Move(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Move(parms); +} + +// Copy file data from parms->filename to parms->destination +// Must only return true if file was copied entirely (Move depends on this being accurate) +bool Copy(File::Filesystem* fs, Stacked::FileParms* parms) { + auto overwrite = parms->flag; + + // Set up virtual paths + auto source = parms->filename; + auto destination = parms->destination; + + // 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_WRITE | 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 + size_t sz_copybuffer = std::min(sz_source, size_t(BC_FILE_SYSTEM_COPYBUFFER_SIZE)); + auto u8_copybuffer = reinterpret_cast(Memory::Allocate(sz_copybuffer)); + + // Loop through the source file, reading segments into copybuffer + for (size_t index = 0; index < sz_source; index += sz_copybuffer) { + // How many bytes to read + size_t sz_bytesToRead = 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, 0); + if (status) { + // Write copied segment to destination file + status = File::Write(st_destination, u8_copybuffer, sz_bytesRead, &sz_bytesWritten, 0); + } + + 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(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Open(parms); +} + +bool Read(File::StreamRecord* file, void* data, int64_t offset, size_t* bytes) { + // File descriptor must be initialized! + BLIZZARD_ASSERT(file != nullptr && file->filefd != -1); + + if (bytes == nullptr || *bytes == 0) { + return true; + } + + // in bytes, the length of the user's read operation. + auto size = *bytes; + // byte index. + int32_t index = 0; + // read status (or count of bytes read) + int32_t result = 0; + + // Fully read the input file according to the count requested. + // Partial reads will be merged together in the end. + while (index < size) { + // Slice length + auto slice = size - index; + + if (offset < 0) { + // Read relative to file pointer + result = read(file->filefd, data, slice); + } else { + // Read absolute + result = pread(file->filefd, data, slice, offset); + } + + auto increment = result; + if (increment < 1) { + increment = 0; + } + + if (offset >= 0) { + offset += increment; + } + + index += increment; + + if (result < 0) { + // append any POSIX failure to the error log + BC_FILE_SET_ERROR_MSG(100 + errno, "Posix Read - %s", file->path); + return false; + } + + if (result == 0) { + // append unexpected EOF to the error log + BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_END_OF_FILE, "Posix Read - End of File - %s", file->path); + return false; + } + + data += increment; + } + + // How many bytes did we read + *bytes = size - index; + + return true; +} + +// Read from parms->stream to void* buffer(parms->param) +// Reading up to #parms->size bytes +// parms->size also stores the result of how many bytes were actually read +bool Read(File::Filesystem* fs, Stacked::FileParms* parms) { + // Position is negative, so actual write position is the current seek offset of the fd. + return Read(parms->stream, parms->param, -1, &parms->size); +} + +// Specify a file position when reading from a file +bool ReadP(File::Filesystem* fs, Stacked::FileParms* parms) { + return Read(parms->stream, parms->param, parms->position, &parms->size); +} + +// Remove an directory +bool RemoveDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::RemoveDirectory(parms); +} + +// Specify disk caching preferences for accessing stream @parms->stream +bool SetCacheMode(File::Filesystem* fs, Stacked::FileParms* parms) { + auto mode = parms->mode; + auto file = parms->stream; + +#if defined(WHOA_SYSTEM_MAC) + // Require user to have clean flags (weird) + BLIZZARD_ASSERT(0 == (mode & ~File::Mode::nocache)); + + File::Flush(file); + + return 0 == fcntl(file->filefd, F_NOCACHE, mode & File::Mode::nocache); +#endif + +#if defined(WHOA_SYSTEM_LINUX) + // TODO: implement equivalent fcntl + return false; +#endif +} + +// Set end of file to parms->position. +bool SetEOF(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetEOF(parms); +} + +// Set file permissions and read/write modes. +bool SetAttributes(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetAttributes(parms); +} + +// Set the file pointer +bool SetPos(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetPos(parms); +} + +// Delete a file +bool Delete(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Delete(parms); +} + +// Write system file specifying options. +bool Write(File::StreamRecord* file, void* data, int64_t offset, size_t* bytes) { + // File descriptor must be initialized! + BLIZZARD_ASSERT(file != nullptr && file->filefd != -1); + + if (bytes == nullptr || *bytes == 0) { + return true; + } + + // in bytes, the length of the user's write operation. + auto size = *bytes; + // byte index. + int32_t index = 0; + // write status (or count of bytes written) + int32_t result = 0; + + // Fully write data buffer to file. + while (index < size) { + // Slice length + auto slice = size - index; + + if (offset < 0) { + // Write relative to current file pointer + result = write(file->filefd, data, slice); + } else { + // Write at an absolute file position + result = pwrite(file->filefd, data, slice, offset); + } + + auto increment = result; + if (increment < 1) { + increment = 0; + } + + if (offset >= 0) { + offset += increment; + } + + index += increment; + + if (result == -1) { + // append any POSIX failure to the error log + BC_FILE_SET_ERROR_MSG(100 + errno, "Posix Write - %s", file->path); + return false; + } + + data += increment; + } + + // How many bytes did we write + *bytes = size - index; + + return true; +} + +// Write parms->param data +bool Write(File::Filesystem* fs, Stacked::FileParms* parms) { + return Write(parms->stream, parms->param, -1, &parms->size); +} + +bool WriteP(File::Filesystem* fs, Stacked::FileParms* parms) { + return Write(parms->stream, parms->param, parms->position, &parms->size); +} + +bool Shutdown(File::Filesystem* fs, Stacked::FileParms* parms) { + // ? + return true; +} + +/*************************************************** +* End POSIX-compatible System_File stack functions * +****************************************************/ + +} // namespace System_File +} // namespace Blizzard diff --git a/bc/system/file/win/Stacked.cpp b/bc/system/file/win/Stacked.cpp new file mode 100644 index 0000000..8cd4185 --- /dev/null +++ b/bc/system/file/win/Stacked.cpp @@ -0,0 +1,811 @@ +#include "bc/file/Defines.hpp" +#include "bc/file/File.hpp" +#include "bc/file/Path.hpp" +#include "bc/file/system/Stacked.hpp" +#include "bc/file/system/win/WinFile.hpp" +#include "bc/Debug.hpp" +#include "bc/Memory.hpp" +#include "bc/String.hpp" + +#include + +/******************************** +* 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(parms->directorySize); + auto directory = static_cast(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(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(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(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) { + auto path = parms->filename; + + char temp[300] = {0}; + auto p = path; + + size_t count = 0; + + while (*p != '\0') { + if (*p == '\\' || *p == '/') { + count++; + int32_t len = p - path; + if (len == 0) { + len = 1; + } + String::Copy(temp, path, len); + temp[len] = '\0'; + + if (::GetFileAttributes(temp) == INVALID_FILE_ATTRIBUTES) { + if (!::CreateDirectory(temp, nullptr)) { + if (::GetLastError() != ERROR_ALREADY_EXISTS) { + return false; + } + } + } + } + p++; + } + + if (count == 0) { + // No directories were made because there are no path separators. + // Make only one directory: + + String::Copy(temp, path, 300); + + if (::GetFileAttributes(temp) == INVALID_FILE_ATTRIBUTES) { + if (!::CreateDirectory(temp, 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 + size_t sz_copybuffer = std::min(sz_source, uint64_t(BC_FILE_SYSTEM_COPYBUFFER_SIZE)); + auto u8_copybuffer = reinterpret_cast(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 = 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, 0); + if (status) { + // Write copied segment to destination file + status = File::Write(st_destination, u8_copybuffer, sz_bytesRead, &sz_bytesWritten, 0); + } + + 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(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(dwRead); + + return true; +} + +bool Read(FileParms* parms) { + return Read(parms->stream, parms->param, -1, &parms->size); +} + +bool ReadP(FileParms* parms) { + return 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(offset); + auto offsetHigh = static_cast(offset >> 32); + + DWORD res = ::SetFilePointer(file->filehandle, offsetLow, &offsetHigh, static_cast(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 * +*********************************/ diff --git a/bc/system/file/win/Support.hpp b/bc/system/file/win/Support.hpp new file mode 100644 index 0000000..6989eb6 --- /dev/null +++ b/bc/system/file/win/Support.hpp @@ -0,0 +1,12 @@ +#ifndef BC_FILE_SYSTEM_WIN_SUPPORT_HPP +#define BC_FILE_SYSTEM_WIN_SUPPORT_HPP + +// Lowercase windows.h macros fight with BlizzardCore file function names +// Get out of here +#if defined(WHOA_SYSTEM_WIN) +#if defined(GetFreeSpace) +#undef GetFreeSpace +#endif +#endif + +#endif diff --git a/bc/system/file/win/System_File.cpp b/bc/system/file/win/System_File.cpp new file mode 100644 index 0000000..5ef4664 --- /dev/null +++ b/bc/system/file/win/System_File.cpp @@ -0,0 +1,142 @@ +#include "bc/file/system/System_File.hpp" +#include "bc/file/system/Stacked.hpp" +#include "bc/file/File.hpp" +#include "bc/file/Defines.hpp" +#include "bc/Debug.hpp" +#include "bc/String.hpp" + +#include + +namespace Blizzard { +namespace System_File { + +/****************************************** +* Begin Win32 System_File stack functions * +*******************************************/ + +// Change current process's working directory to parms->filename. +bool SetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetWorkingDirectory(parms); +} + +// Close parms->stream file handle. +bool Close(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Close(parms); +} + +bool Create(File::Filesystem* fs, Stacked::FileParms* parms) { + // System_File::FileError(9) + BC_FILE_SET_ERROR(BC_FILE_ERROR_UNIMPLEMENTED); + return false; +} + +bool GetWorkingDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetWorkingDirectory(parms); +} + +bool ProcessDirFast(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::ProcessDirFast(parms); +} + +bool Exists(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Exists(parms); +} + +bool Flush(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Flush(parms); +} + +bool GetFileInfo(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetFileInfo(parms); +} + +bool GetFreeSpace(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetFreeSpace(parms); +} + +bool GetPos(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetPos(parms); +} + +bool GetRootChars(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::GetRootChars(parms); +} + +bool IsAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::IsAbsolutePath(parms); +} + +bool IsReadOnly(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::IsReadOnly(parms); +} + +bool MakeAbsolutePath(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::MakeAbsolutePath(parms); +} + +bool CreateDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::CreateDirectory(parms); +} + +bool Move(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Move(parms); +} + +bool Copy(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Copy(parms); +} + +bool Open(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Open(parms); +} + +bool Read(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Read(parms); +} + +bool ReadP(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::ReadP(parms); +} + +bool RemoveDirectory(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::RemoveDirectory(parms); +} + +bool SetCacheMode(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetCacheMode(parms); +} + +bool SetEOF(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetEOF(parms); +} + +bool SetAttributes(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetAttributes(parms); +} + +bool SetPos(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::SetPos(parms); +} + +bool Delete(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Delete(parms); +} + +bool Write(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::Write(parms); +} + +bool WriteP(File::Filesystem* fs, Stacked::FileParms* parms) { + return Stacked::WriteP(parms); +} + +bool Shutdown(File::Filesystem* fs, Stacked::FileParms* parms) { + return false; +} + +/**************************************** +* End Win32 System_File stack functions * +*****************************************/ + +} // namespace System_File +} // namespace Blizzard diff --git a/bc/system/file/win/WinFile.cpp b/bc/system/file/win/WinFile.cpp new file mode 100644 index 0000000..fd2a780 --- /dev/null +++ b/bc/system/file/win/WinFile.cpp @@ -0,0 +1,181 @@ +#include "bc/file/Defines.hpp" +#include "bc/file/system/win/Utils.hpp" + +#include + +namespace Blizzard { +namespace WinFile { + +// Translate Win32 file bits into Blizzard file bits. +uint32_t AttributesToBC(DWORD dwFileAttributes) { + uint32_t fileAttributes = 0; + if (dwFileAttributes & FILE_ATTRIBUTE_READONLY) { + fileAttributes |= BC_FILE_ATTRIBUTE_READONLY; + } + + if (dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) { + fileAttributes |= BC_FILE_ATTRIBUTE_HIDDEN; + } + + if (dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) { + fileAttributes |= BC_FILE_ATTRIBUTE_SYSTEM; + } + + if (dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) { + fileAttributes |= BC_FILE_ATTRIBUTE_TEMPORARY; + } + + if (dwFileAttributes & FILE_ATTRIBUTE_NORMAL) { + fileAttributes |= BC_FILE_ATTRIBUTE_NORMAL; + } + + if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + fileAttributes |= BC_FILE_ATTRIBUTE_DIRECTORY; + } + + return fileAttributes; +} + +// Translate Blizzard file attribute bits into Win32 attribute bits. +DWORD AttributesToWin(uint32_t fileAttributes) { + DWORD dwFileAttributes = 0; + + if (fileAttributes & BC_FILE_ATTRIBUTE_READONLY) { + dwFileAttributes |= FILE_ATTRIBUTE_READONLY; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_HIDDEN) { + dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_SYSTEM) { + dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_ARCHIVE) { + dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_TEMPORARY) { + dwFileAttributes |= FILE_ATTRIBUTE_TEMPORARY; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_NORMAL) { + dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; + } + + if (fileAttributes & BC_FILE_ATTRIBUTE_DIRECTORY) { + dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } + + return dwFileAttributes; +} + +// Convert FILETIME (two 32-bit values) into 64-bit unsigned integer. +uint64_t UnpackTime(FILETIME ft) { + ULARGE_INTEGER ul = {}; + ul.HighPart = ft.dwHighDateTime; + ul.LowPart = ft.dwLowDateTime; + return static_cast(ul.QuadPart); +} + +// Convert 64-bit unsigned integer into FILETIME (two 32-bit values) +FILETIME PackTime(uint64_t qw) { + ULARGE_INTEGER ul = {}; + ul.QuadPart = qw; + FILETIME ft = {}; + ft.dwHighDateTime = ul.HighPart; + ft.dwLowDateTime = ul.LowPart; + return ft; +} + +// Returns true if BY_HANDLE_FILE_INFORMATION is successfully converted to File::FileInfo. +bool HandleFileInfoToBC( + LPBY_HANDLE_FILE_INFORMATION winInfo, + Blizzard::File::FileInfo* info) { + if (!info) { + return false; + } + + info->attributes = AttributesToBC(winInfo->dwFileAttributes); + info->device = static_cast(winInfo->dwVolumeSerialNumber); + info->size = (static_cast(winInfo->nFileSizeHigh) << 32ULL) | static_cast(winInfo->nFileSizeLow); + + info->accessTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftLastAccessTime)); + info->modificationTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftLastWriteTime)); + info->attributeModificationTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftCreationTime)); + + return true; +} + +// Returns true if WIN32_FILE_ATTRIBUTE_DATA is successfully converted to File::FileInfo. +bool AttributeFileInfoToBC( + LPWIN32_FILE_ATTRIBUTE_DATA winInfo, + Blizzard::File::FileInfo* info) { + + if (!info) { + return false; + } + + info->attributes = AttributesToBC(winInfo->dwFileAttributes); + info->device = 0; + info->size = (static_cast(winInfo->nFileSizeHigh) << 32ULL) | static_cast(winInfo->nFileSizeLow); + + info->accessTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftLastAccessTime)); + info->modificationTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftLastWriteTime)); + info->attributeModificationTime = Blizzard::Time::FromWinFiletime(UnpackTime(winInfo->ftCreationTime)); + + return true; +} + +// Open a HANDLE to a file, using BlizzardCore flags. +HANDLE Open(const char* systemPath, uint32_t flags, bool nocache) { + // expand flags + bool read = flags & BC_FILE_OPEN_READ; + bool write = flags & BC_FILE_OPEN_WRITE; + bool shareRead = flags & BC_FILE_OPEN_SHARE_READ; + bool shareRead = flags & BC_FILE_OPEN_SHARE_WRITE; + bool write = flags & BC_FILE_OPEN_WRITE; + bool mustNotExist = flags & BC_FILE_OPEN_MUST_NOT_EXIST; + bool mustExist = flags & BC_FILE_OPEN_MUST_EXIST; + bool create = flags & BC_FILE_OPEN_CREATE; + bool truncate = flags & BC_FILE_OPEN_TRUNCATE; + + // BLIZZARD_ASSERT(read || write); + + // Start building arguments to CreateFile() + DWORD desiredAccess = 0; + DWORD shareMode = 0; + DWORD createDisposition = 0; + DWORD flagsAndAttributes = 0; + + if (nocache) { + flagsAndAttributes |= FILE_FLAG_WRITE_THROUGH; + } + + // Setup desired access + if (read) { + desiredAccess |= GENERIC_READ; + } + if (write) { + desiredAccess |= GENERIC_WRITE; + } + + // Setup create disposition + if (create && mustNotExist) { + createDisposition = CREATE_NEW; + } else if (create && !mustNotExist) { + createDisposition = CREATE_ALWAYS; + } else if (truncate) { + createDisposition = TRUNCATE_EXISTING; + } else if (mustExist) { + createDisposition = OPEN_EXISTING; + } else { + createDisposition = OPEN_ALWAYS; + } + + return CreateFileA(systemPath, desiredAccess, shareMode, nullptr, createDisposition, flagsAndAttributes, 0); +} + +} // namespace WinFile +} // namespace Blizzard diff --git a/bc/system/file/win/WinFile.hpp b/bc/system/file/win/WinFile.hpp new file mode 100644 index 0000000..5fef50a --- /dev/null +++ b/bc/system/file/win/WinFile.hpp @@ -0,0 +1,36 @@ +#ifndef BC_FILE_SYSTEM_WIN_UTILS_HPP +#define BC_FILE_SYSTEM_WIN_UTILS_HPP + +#include "bc/file/Types.hpp" + +#include +#include + +namespace Blizzard { +namespace WinFile { + +// Translate Win32 file bits into Blizzard file bits. +uint32_t AttributesToBC(DWORD dwFileAttributes); + +DWORD AttributesToWin(uint32_t attributes); + +uint64_t UnpackTime(FILETIME ft); + +FILETIME PackTime(uint64_t qw); + +// Returns true if BY_HANDLE_FILE_INFORMATION is successfully converted to File::FileInfo. +bool HandleFileInfoToBC( + LPBY_HANDLE_FILE_INFORMATION winInfo, + Blizzard::File::FileInfo* info); + +// Returns true if WIN32_FILE_ATTRIBUTE_DATA is successfully converted to File::FileInfo. +bool AttributeFileInfoToBC( + LPWIN32_FILE_ATTRIBUTE_DATA winInfo, + Blizzard::File::FileInfo* info); + +HANDLE Open(const char* systemPath, uint32_t flags, bool nocache); + +} // namespace WinFile +} // namespace Blizzard + +#endif