feat(file): implement filesystem utilities

This commit is contained in:
phaneron 2023-08-09 21:34:43 -04:00
parent 5e6af0ea70
commit bd65df59e9
26 changed files with 4163 additions and 5 deletions

View file

@ -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}
)

View file

@ -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 {

View file

@ -1,7 +1,61 @@
#include "bc/String.hpp"
#include "bc/Debug.hpp"
#include <cstring>
#include <cstdio>
#include <cstdarg>
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<uintptr_t>(outBytes) - reinterpret_cast<uintptr_t>(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

View file

@ -3,15 +3,63 @@
#include <cstdint>
#include <cstdlib>
#include <cstdarg>
#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<size_t Cap>
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 <size_t Cap>
QuickFormat<Cap>::QuickFormat(const char* format, ...) {
va_list args;
va_start(args, format);
VFormat(this->buffer, Cap, format, args);
}
template <size_t Cap>
const char* QuickFormat<Cap>::Str() {
return static_cast<const char*>(this->buffer);
}
} // namespace String
} // namespace Blizzard

52
bc/file/Defines.hpp Normal file
View file

@ -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<int32_t>(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

332
bc/file/File.cpp Normal file
View file

@ -0,0 +1,332 @@
#include "bc/file/File.hpp"
#include "bc/file/Filesystem.hpp"
#include "bc/file/system/Stacked.hpp"
#include <cstdio>
#include <cinttypes>
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<uint32_t>(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<size_t>(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<size_t>(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

54
bc/file/File.hpp Normal file
View file

@ -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

21
bc/file/Filesystem.cpp Normal file
View file

@ -0,0 +1,21 @@
#include "bc/file/Filesystem.hpp"
#include <cstddef>
namespace Blizzard {
namespace File {
bool Filesystem::Do(Filesystem::Call fscall, System_File::Stacked::FileParms* parms) {
uint32_t callindex = static_cast<uint32_t>(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<Filesystem::CallFunc>(this->funcs[callindex]);
// Do call
return func(this, parms);
}
} // namespace File
} // namespace Blizzard

66
bc/file/Filesystem.hpp Normal file
View file

@ -0,0 +1,66 @@
#ifndef BC_FILE_FILESYSTEM_HPP
#define BC_FILE_FILESYSTEM_HPP
#include "bc/file/system/Types.hpp"
#include <cstdint>
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<size_t>(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

194
bc/file/Path.cpp Normal file
View file

@ -0,0 +1,194 @@
#include "bc/file/Path.hpp"
#include "bc/String.hpp"
#include "bc/Memory.hpp"
#include "bc/Debug.hpp"
#include <cctype>
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<char*>(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

51
bc/file/Path.hpp Normal file
View file

@ -0,0 +1,51 @@
#ifndef BC_FILE_PATH_HPP
#define BC_FILE_PATH_HPP
#include "bc/file/Defines.hpp"
#include <cstddef>
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

76
bc/file/Types.hpp Normal file
View file

@ -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 <windows.h>
#endif
#include <cstddef>
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

146
bc/os/File.cpp Normal file
View file

@ -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<Blizzard::File::StreamRecord*>(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<Blizzard::File::StreamRecord*>(fileHandle));
}

65
bc/os/File.hpp Normal file
View file

@ -0,0 +1,65 @@
#ifndef BC_OS_FILE_HPP
#define BC_OS_FILE_HPP
#include "bc/file/Types.hpp"
#include <cstdint>
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

View file

@ -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

291
bc/system/file/Stacked.cpp Normal file
View file

@ -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<File::Filesystem*>(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<File::Filesystem*>(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<File::Filesystem::CallFunc*>(reinterpret_cast<uintptr_t>(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

View file

@ -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 <cstdint>
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

View file

@ -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 <cstdint>
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

70
bc/system/file/Types.hpp Normal file
View file

@ -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 <cstdint>
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

View file

@ -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 <cerrno>
#include <cstdio>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
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<File::StreamRecord*>(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<char*>(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<char*>(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<char*>(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<int32_t>(previous_character - local_1054)) + 1);
if (parms->directorySize <= BC_FILE_MAX_PATH) {
next_slash = next_slash_fast;
} else {
next_slash = reinterpret_cast<char*>(Memory::Allocate(parms->directorySize));
}
if (::realpath(copied_univ_path, next_slash) == nullptr) {
temp_directory = temp_directory + (static_cast<int32_t>(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<off_t>(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

View file

@ -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 <algorithm>
#include <cerrno>
#include <unistd.h>
#include <sys/stat.h>
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<uint32_t>(info.st_dev);
infoptr->size = static_cast<uint64_t>(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<uint64_t>(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/<your volume>
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<uintptr_t>(volume) - reinterpret_cast<uintptr_t>(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<uint8_t*>(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

View file

@ -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 <algorithm>
/********************************
* Begin Win32 Stacked functions *
*********************************/
namespace Blizzard {
namespace System_File {
namespace Stacked {
bool SetWorkingDirectory(FileParms* parms) {
BLIZZARD_ASSERT(parms->filename);
return ::SetCurrentDirectory(parms->filename) != 0;
}
bool Close(FileParms* parms) {
auto file = parms->stream;
BLIZZARD_ASSERT(file != nullptr);
::CloseHandle(file->filehandle);
Memory::Free(file);
return true;
}
bool GetWorkingDirectory(FileParms* parms) {
if (!parms->directory || !parms->directorySize) {
// System_File::FileError(8)
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto size = static_cast<DWORD>(parms->directorySize);
auto directory = static_cast<LPSTR>(parms->directory);
::GetCurrentDirectory(size, directory);
return true;
}
bool ProcessDirFast(FileParms* parms) {
// Get parameters
auto unkflag = parms->flag;
auto directory = parms->filename;
auto callback = parms->callback;
auto param = parms->param;
// Set up walk parameters
File::ProcessDirParms processDirParms;
processDirParms.root = directory;
processDirParms.param = param;
constexpr uint32_t formatSize = BC_FILE_MAX_PATH * 2;
char formatted[formatSize] = "";
// Tack on a backslash as well as an asterisk.
String::Format(formatted, formatSize, "%s\\*", processDirParms.root);
// Convert potentially large path to universal format
BC_FILE_PATH(path);
if (!File::Path::MakeNativePath(formatted, path, formatSize)) {
return false;
}
//
WIN32_FIND_DATA findData;
auto hFindFile = ::FindFirstFile(formatted, &findData);
if (hFindFile == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_INVALID_ARGUMENT, "Win32 ProcessDirFast - %s", directory);
return false;
}
BC_FILE_PATH(currentPath);
processDirParms.item = currentPath;
while (true) {
// Ignore .. and .
if (findData.cFileName[0] != '.' || findData.cFileName[1] && (findData.cFileName[1] != '.' || findData.cFileName[2])) {
String::Copy(currentPath, findData.cFileName, BC_FILE_MAX_PATH);
processDirParms.itemIsDirectory = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
if (!callback(processDirParms)) {
::FindClose(hFindFile);
return true;
}
if (!::FindNextFile(hFindFile, &findData)) {
break;
}
}
}
::FindClose(hFindFile);
return false;
}
bool Exists(FileParms* parms) {
auto filepath = parms->filename;
if (!filepath) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative filepathNative(filepath);
auto dwFileAttributes = ::GetFileAttributes(static_cast<LPCSTR>(filepathNative.Str()));
if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
parms->info->attributes = 0;
return false;
}
uint32_t fileAttributes = WinFile::AttributesToBC(dwFileAttributes);
parms->info->attributes = fileAttributes;
return fileAttributes & BC_FILE_ATTRIBUTE_NORMAL;
}
bool Flush(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE ) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto status = ::FlushFileBuffers(file->filehandle);
return status != 0;
}
bool GetFileInfo(FileParms* parms) {
auto file = parms->stream;
auto filepath = parms->filename;
auto info = parms->info;
if (filepath == nullptr && file == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
// StreamRecord* file may be used to supply storage of file info. (StreamRecord*->info)
if (file) {
BLIZZARD_ASSERT(file->filehandle != INVALID_HANDLE_VALUE);
if (info == nullptr) {
// telling this function to save file info into StreamRecord
info = &file->info;
}
}
if (filepath) {
// Fetch info based on filepath.
static File::FileInfo s_noinfo = {};
if (info == nullptr) {
info = &s_noinfo;
}
File::Path::QuickNative filepathNative(filepath);
WIN32_FILE_ATTRIBUTE_DATA fileAttributeData = {};
// Read attributes
if (!::GetFileAttributesEx(filepathNative.Str(), GetFileExInfoStandard, &fileAttributeData)) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_INVALID_HANDLE, "Win32 GetFileInfo - GetFileAttributesExA failed");
return false;
}
return WinFile::AttributeFileInfoToBC(&fileAttributeData, info);
}
// Fetch info using opened file handle
auto filehandle = file->filehandle;
BY_HANDLE_FILE_INFORMATION byHandleInfo;
if (!::GetFileInformationByHandle(filehandle, &byHandleInfo)) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_HANDLE);
return false;
}
file->hasInfo = true;
return WinFile::HandleFileInfoToBC(&byHandleInfo, info);
}
bool GetFreeSpace(FileParms* parms) {
auto filename = parms->filename;
if (filename == nullptr || *filename == '\0') {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto len = String::Length(filename) + 2;
BC_FILE_PATH(systemPath);
// UNC convert
File::Path::MakeNativePath(filename, systemPath, BC_FILE_MAX_PATH);
// GetDiskFreeSpaceExA will fail without a trailing backslash
File::Path::ForceTrailingSeparator(systemPath, BC_FILE_MAX_PATH, BC_FILE_SYSTEM_PATH_SEPARATOR);
ULARGE_INTEGER freeBytesAvailableToCaller = {};
ULARGE_INTEGER totalNumberOfBytes = {};
ULARGE_INTEGER totalNumberOfFreeBytes = {};
if (!GetDiskFreeSpaceExA(systemPath, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
parms->size64 = static_cast<uint64_t>(freeBytesAvailableToCaller.QuadPart);
return true;
}
bool GetPos(FileParms* parms) {
auto file = parms->stream;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
LONG high = 0;
DWORD low = ::SetFilePointer(file->filehandle, 0, &high, FILE_CURRENT);
if (low == -1 && GetLastError()) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_HANDLE);
return false;
}
ULARGE_INTEGER ul = {};
ul.LowPart = low;
ul.HighPart = high;
parms->position = ul.QuadPart;
return true;
}
bool GetRootChars(FileParms* parms) {
char cVar1 = '\0';
int32_t counter = 0;
int32_t size = 0;
char *buffer = nullptr;
const char *path = nullptr;
char unixPathFast[256] = {0};
const char *end = nullptr;
path = parms->filename;
counter = String::Length(path);
size = counter + 1;
if (size > 256) {
buffer = reinterpret_cast<char *>(Memory::Allocate(size));
} else {
buffer = unixPathFast;
}
// Invalid without error
if (size < 0x2) {
parms->end = 0;
parms->beginning = 0;
} else {
File::Path::MakeUnivPath(path, buffer, size);
path = buffer + 1;
// Get DOS canonical path
if (buffer[1] == ':') {
cVar1 = buffer[2];
parms->beginning = 0;
parms->end = (cVar1 == '/') + 2;
} else {
// Get UNC path
if ((*buffer == '/') && (buffer[1] == '/')) {
counter = 0;
do {
path = strchr(path + 1, '$');
if (path == nullptr) {
parms->end = size;
parms->beginning = 0;
if (buffer != unixPathFast) {
Memory::Free(buffer);
}
return true;
}
counter = counter + 1;
} while (counter < 2);
parms->end = intptr_t(path) + (1 - (intptr_t)buffer);
} else {
parms->end = 0;
}
parms->beginning = 0;
}
}
if (buffer != unixPathFast) {
Memory::Free(buffer);
}
return true;
}
bool IsAbsolutePath(FileParms* parms) {
auto path = parms->filename;
if (!path) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
auto first = path[0];
if (
// UNC
(
((first == '\\') || (first == '/')) &&
((path[1] == '\\' || (path[1] == '/')))
) &&
(path[2] != '\0')) {
return true;
}
// DOS canonical
if (isalpha(first) && (path[1] == ':')) {
if (((path[2] == '\\') || (path[2] == '/')) && (path[3] != '\0')) {
return true;
}
}
return false;
}
bool IsReadOnly(FileParms* parms) {
auto filepath = parms->filename;
if (!filepath) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative filepathNative(filepath);
auto dwFileAttributes = ::GetFileAttributes(filepathNative.Str());
if (dwFileAttributes == INVALID_FILE_ATTRIBUTES) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
return (dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0;
}
bool MakeAbsolutePath(FileParms* parms) {
BC_FILE_PATH(full);
auto path = parms->filename;
if (path == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
::_fullpath(full, path, BC_FILE_MAX_PATH);
File::Path::ForceTrailingSeparator(full, BC_FILE_MAX_PATH, BC_FILE_SYSTEM_PATH_SEPARATOR);
String::Copy(parms->directory, full, parms->directorySize);
return true;
}
bool CreateDirectory(FileParms* parms) {
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<uint8_t*>(Memory::Allocate(sz_copybuffer));
// Loop through the source file, reading segments into copybuffer
for (uint64_t index = 0; index < sz_source; index += sz_copybuffer) {
// How many bytes to read
size_t sz_bytesToRead = 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<File::StreamRecord*>(fileData);
file->flags = flags;
file->filehandle = handle;
String::Copy(file->path, path, pathNative.Size());
File::GetFileInfo(file);
parms->stream = file;
return true;
}
bool Read(File::StreamRecord* file, void* data, int64_t offset, size_t* bytes) {
// File descriptor must be initialized!
BLIZZARD_ASSERT(file != nullptr && file->filehandle != INVALID_HANDLE_VALUE);
if (bytes == nullptr || *bytes == 0) {
return true;
}
if (offset > -1) {
if (!File::SetPos(file, offset, BC_FILE_SEEK_START)) {
return false;
}
}
DWORD dwRead = 0;
BOOL ok = ::ReadFile(file->filehandle, data, *bytes, &dwRead, nullptr);
if (ok == 0) {
// append any Win32 failure to the error log
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_BAD_FILE, "Win32 Read %p - %s", GetLastError(), file->path);
return false;
}
*bytes = static_cast<size_t>(dwRead);
return true;
}
bool Read(FileParms* parms) {
return 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<LONG>(offset);
auto offsetHigh = static_cast<LONG>(offset >> 32);
DWORD res = ::SetFilePointer(file->filehandle, offsetLow, &offsetHigh, static_cast<DWORD>(whence));
if (res == INVALID_SET_FILE_POINTER) {
res = GetLastError();
if (res != 0) {
return false;
}
}
return true;
}
bool Delete(FileParms* parms) {
auto deletePath = parms->filename;
if (deletePath == nullptr) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
File::Path::QuickNative deletePathNative(deletePath);
BOOL ok = ::DeleteFile(deletePathNative.Str());
return ok != 0;
}
bool Write(FileParms* parms) {
auto file = parms->stream;
auto data = parms->param;
auto size = parms->size;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
DWORD numberOfBytesWritten = 0;
BOOL ok = WriteFile(file->filehandle, data, size, &numberOfBytesWritten, nullptr);
return ok != 0;
}
bool WriteP(FileParms* parms) {
auto file = parms->stream;
auto data = parms->param;
auto position = parms->position;
auto size = parms->size;
if (file == nullptr || file->filehandle == INVALID_HANDLE_VALUE) {
BC_FILE_SET_ERROR(BC_FILE_ERROR_INVALID_ARGUMENT);
return false;
}
int64_t origpos = 0;
if (!File::GetPos(file, origpos)) {
return false;
}
if (!File::SetPos(file, position, BC_FILE_SEEK_START)) {
return false;
}
DWORD numberOfBytesWritten = 0;
BOOL ok = WriteFile(file->filehandle, data, size, &numberOfBytesWritten, nullptr);
if (ok == 0) {
BC_FILE_SET_ERROR_MSG(BC_FILE_ERROR_BAD_FILE, "Win32 Write - %s", file->path);
return false;
}
return File::SetPos(file, origpos, BC_FILE_SEEK_START);
}
} // namespace Stacked
} // namespace System_File
} // namespace Blizzard
/********************************
* End of Win32 Stacked functions *
*********************************/

View file

@ -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

View file

@ -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 <storm/String.hpp>
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

View file

@ -0,0 +1,181 @@
#include "bc/file/Defines.hpp"
#include "bc/file/system/win/Utils.hpp"
#include <windows.h>
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<uint64_t>(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<uint32_t>(winInfo->dwVolumeSerialNumber);
info->size = (static_cast<uint64_t>(winInfo->nFileSizeHigh) << 32ULL) | static_cast<uint64_t>(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<uint64_t>(winInfo->nFileSizeHigh) << 32ULL) | static_cast<uint64_t>(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

View file

@ -0,0 +1,36 @@
#ifndef BC_FILE_SYSTEM_WIN_UTILS_HPP
#define BC_FILE_SYSTEM_WIN_UTILS_HPP
#include "bc/file/Types.hpp"
#include <windows.h>
#include <cstdint>
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