mirror of
https://github.com/thunderbrewhq/bc.git
synced 2025-12-12 18:12:29 +00:00
feat(file): implement filesystem utilities
This commit is contained in:
parent
5e6af0ea70
commit
bd65df59e9
26 changed files with 4163 additions and 5 deletions
|
|
@ -1,10 +1,23 @@
|
||||||
file(GLOB BC_SOURCES
|
file(GLOB BC_SOURCES
|
||||||
"*.cpp"
|
"*.cpp"
|
||||||
"lock/*.cpp"
|
"lock/*.cpp"
|
||||||
|
"os/*.cpp"
|
||||||
|
"file/*.cpp"
|
||||||
"time/*.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
|
add_library(bc STATIC
|
||||||
${BC_SOURCES}
|
${BC_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,13 @@
|
||||||
(void)0
|
(void)0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define BLIZZARD_VALIDATE(x, y, ...) \
|
||||||
|
if (!(x)) { \
|
||||||
|
Blizzard::Debug::Assert(!y, __FILE__, __LINE__); \
|
||||||
|
return __VA_ARGS__; \
|
||||||
|
} \
|
||||||
|
(void)0
|
||||||
|
|
||||||
namespace Blizzard {
|
namespace Blizzard {
|
||||||
namespace Debug {
|
namespace Debug {
|
||||||
|
|
||||||
|
|
|
||||||
235
bc/String.cpp
235
bc/String.cpp
|
|
@ -1,7 +1,61 @@
|
||||||
#include "bc/String.hpp"
|
#include "bc/String.hpp"
|
||||||
|
#include "bc/Debug.hpp"
|
||||||
#include <cstring>
|
#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) {
|
if (!len || !dst) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +82,7 @@ int32_t Blizzard::String::Copy(char* dst, const char* src, size_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v4 = (v5++)[1];
|
v4 = (v5++)[1];
|
||||||
} while ( v4 );
|
} while (v4);
|
||||||
|
|
||||||
result = v6 - dst;
|
result = v6 - dst;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -41,7 +95,89 @@ int32_t Blizzard::String::Copy(char* dst, const char* src, size_t len) {
|
||||||
return result;
|
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) {
|
if (str) {
|
||||||
return strlen(str);
|
return strlen(str);
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +185,97 @@ uint32_t Blizzard::String::Length(const char* str) {
|
||||||
return 0;
|
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);
|
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
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,63 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#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 Blizzard {
|
||||||
namespace String {
|
namespace String {
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
template<size_t Cap>
|
||||||
|
class QuickFormat {
|
||||||
|
public:
|
||||||
|
char buffer[Cap];
|
||||||
|
|
||||||
|
QuickFormat(const char* format, ...);
|
||||||
|
const char* Str();
|
||||||
|
};
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
int32_t Append(char* dst, const char* src, size_t cap);
|
||||||
|
|
||||||
int32_t Copy(char* dst, const char* src, size_t len);
|
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);
|
uint32_t Length(const char* str);
|
||||||
|
|
||||||
void MemFill(void* dst, uint32_t len, uint8_t fill);
|
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 String
|
||||||
} // namespace Blizzard
|
} // namespace Blizzard
|
||||||
|
|
||||||
|
|
|
||||||
52
bc/file/Defines.hpp
Normal file
52
bc/file/Defines.hpp
Normal 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
332
bc/file/File.cpp
Normal 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
54
bc/file/File.hpp
Normal 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
21
bc/file/Filesystem.cpp
Normal 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
66
bc/file/Filesystem.hpp
Normal 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
194
bc/file/Path.cpp
Normal 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
51
bc/file/Path.hpp
Normal 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
76
bc/file/Types.hpp
Normal 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
146
bc/os/File.cpp
Normal 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
65
bc/os/File.hpp
Normal 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
|
||||||
13
bc/system/file/Defines.hpp
Normal file
13
bc/system/file/Defines.hpp
Normal 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
291
bc/system/file/Stacked.cpp
Normal 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
|
||||||
85
bc/system/file/Stacked.hpp
Normal file
85
bc/system/file/Stacked.hpp
Normal 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
|
||||||
51
bc/system/file/System_File.hpp
Normal file
51
bc/system/file/System_File.hpp
Normal 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
70
bc/system/file/Types.hpp
Normal 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
|
||||||
602
bc/system/file/posix/Stacked.cpp
Normal file
602
bc/system/file/posix/Stacked.cpp
Normal 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
|
||||||
512
bc/system/file/posix/System_File.cpp
Normal file
512
bc/system/file/posix/System_File.cpp
Normal 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
|
||||||
811
bc/system/file/win/Stacked.cpp
Normal file
811
bc/system/file/win/Stacked.cpp
Normal 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 *
|
||||||
|
*********************************/
|
||||||
12
bc/system/file/win/Support.hpp
Normal file
12
bc/system/file/win/Support.hpp
Normal 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
|
||||||
142
bc/system/file/win/System_File.cpp
Normal file
142
bc/system/file/win/System_File.cpp
Normal 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
|
||||||
181
bc/system/file/win/WinFile.cpp
Normal file
181
bc/system/file/win/WinFile.cpp
Normal 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
|
||||||
36
bc/system/file/win/WinFile.hpp
Normal file
36
bc/system/file/win/WinFile.hpp
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue