diff --git a/bc/CMakeLists.txt b/bc/CMakeLists.txt index 86e1c53..584e8d9 100644 --- a/bc/CMakeLists.txt +++ b/bc/CMakeLists.txt @@ -3,6 +3,7 @@ file(GLOB BC_SOURCES "lock/*.cpp" "os/*.cpp" "file/*.cpp" + "file/path/*.cpp" "time/*.cpp" "system/*.cpp" "system/file/*.cpp" diff --git a/bc/file/Path.hpp b/bc/file/Path.hpp index 20042a3..f23a5c2 100644 --- a/bc/file/Path.hpp +++ b/bc/file/Path.hpp @@ -1,51 +1 @@ -#ifndef BC_FILE_PATH_HPP -#define BC_FILE_PATH_HPP - -#include "bc/file/Defines.hpp" - -#include - -namespace Blizzard { -namespace File { -namespace Path { - -// Classes - -// Quick native path translation on the stack. -// Can be slow in some instances, as it resorts to heap allocation if path size >= BC_FILE_MAX_PATH -// Note: this name is an invention, however its behavior is repeated in all System_File implementations. -// So it probably did exist as an inlined class. -class QuickNative { - private: - size_t size; - // stack allocation - char fastpath[BC_FILE_MAX_PATH]; - // heap - char* fatpath; - - public: - QuickNative(const char* path); - ~QuickNative(); - const char* Str(); - size_t Size(); -}; - -// Functions - -void ForceTrailingSeparator(char* buf, size_t bufMax, char sep); - -bool MakeBackslashPath(const char* path, char* result, size_t capacity); - -bool MakeConsistentPath(const char* path, char* result, size_t capacity); - -bool MakeNativePath(const char* path, char* result, size_t capacity); - -bool MakeUnivPath(const char* path, char* result, size_t capacity); - -bool MakeWindowsPath(const char* path, char* result, size_t capacity); - -} // namespace Path -} // namespace File -} // namespace Blizzard - -#endif +#include "bc/file/path/Path.hpp" diff --git a/bc/file/path/Path.cpp b/bc/file/path/Path.cpp new file mode 100644 index 0000000..76f6ece --- /dev/null +++ b/bc/file/path/Path.cpp @@ -0,0 +1,225 @@ +#include "bc/file/path/Path.hpp" +#include "bc/file/path/Posix.hpp" +#include "bc/String.hpp" +#include "bc/Memory.hpp" +#include "bc/Debug.hpp" + +#include + +namespace Blizzard { +namespace File { +namespace Path { + +// Classes + +QuickNative::QuickNative(const char* path) { + this->size = 0; + this->slowpath = nullptr; + this->fastpath[0] = '\0'; + + if (!path) { + return; + } + + // Null byte + constexpr size_t reserved = 1; + + this->size = String::Length(path) + reserved; + + char* nativePath = nullptr; + + if (this->size < BC_FILE_MAX_PATH) { + // (fast) + nativePath = this->fastpath; + MakeNativePath(path, this->fastpath, BC_FILE_MAX_PATH); + } else { + // (slow) + this->slowpath = reinterpret_cast(Memory::Allocate(this->size)); + nativePath = this->slowpath; + MakeNativePath(path, this->slowpath, this->size); + } + +#if defined(WHOA_SYSTEM_LINUX) + // Linux typically does not involve a case-sensitive filesystem + // For compatibility, try to find if this path exists in another case + // This is much slower + + // The size of the resolved path. Maybe be larger due to "./" + size_t resolvedSize = this->size + 2; + + auto resolvedPath = static_cast(Memory::Allocate(resolvedSize)); + resolvedPath[resolvedSize-1] = '\0'; + + bool wasResolved = ResolvePosixCasePath(nativepath, resolvedPath); + if (!wasResolved) { + Memory::Free(resolvedPath); + return; + } + + if (this->slowpath != nullptr) { + Memory::Free(this->slowpath); + } + + this->slowpath = resolvedPath; +#endif +} + +QuickNative::~QuickNative() { + if (this->slowpath != nullptr) { + Memory::Free(this->slowpath); + } +} + +const char* QuickNative::Str() { + if (this->slowpath != nullptr) { + return this->slowpath; + } + + 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]; + + i++; + } + + result[0] = '\0'; + return false; +} + +bool MakeWindowsPath(const char* path, char* result, size_t capacity) { + return MakeBackslashPath(path, result, capacity); +} + +} // namespace Path +} // namespace File +} // namespace Blizzard diff --git a/bc/file/path/Path.hpp b/bc/file/path/Path.hpp new file mode 100644 index 0000000..fad9a2a --- /dev/null +++ b/bc/file/path/Path.hpp @@ -0,0 +1,51 @@ +#ifndef BC_FILE_PATH_PATH_HPP +#define BC_FILE_PATH_PATH_HPP + +#include "bc/file/Defines.hpp" + +#include + +namespace Blizzard { +namespace File { +namespace Path { + +// Classes + +// Quick native path translation on the stack. +// Can be slow in some instances, as it resorts to heap allocation if path size >= BC_FILE_MAX_PATH +// Note: this name is an invention, however its behavior is repeated in all System_File implementations. +// So it probably did exist as an inlined class. +class QuickNative { + private: + size_t size; + // stack allocation + char fastpath[BC_FILE_MAX_PATH]; + // heap + char* slowpath; + + public: + QuickNative(const char* path); + ~QuickNative(); + const char* Str(); + size_t Size(); +}; + +// Functions + +void ForceTrailingSeparator(char* buf, size_t bufMax, char sep); + +bool MakeBackslashPath(const char* path, char* result, size_t capacity); + +bool MakeConsistentPath(const char* path, char* result, size_t capacity); + +bool MakeNativePath(const char* path, char* result, size_t capacity); + +bool MakeUnivPath(const char* path, char* result, size_t capacity); + +bool MakeWindowsPath(const char* path, char* result, size_t capacity); + +} // namespace Path +} // namespace File +} // namespace Blizzard + +#endif diff --git a/bc/file/path/Posix.cpp b/bc/file/path/Posix.cpp new file mode 100644 index 0000000..445314e --- /dev/null +++ b/bc/file/path/Posix.cpp @@ -0,0 +1,75 @@ +#include "bc/file/path/Posix.hpp" + +#if defined(WHOA_SYSTEM_LINUX) +#include +#include +#include +#include +#include + +// adapted from https://github.com/OneSadCookie/fcaseopen/blob/master/fcaseopen.c +bool ResolvePosixCasePath(const char* path, char* r) { + auto l = strlen(path); + auto p = static_cast(alloca(l + 1)); + strcpy(p, path); + size_t rl = 0; + + DIR* d; + if (p[0] == '/') { + d = opendir("/"); + p = p + 1; + } else { + d = opendir("."); + r[0] = '.'; + r[1] = 0; + rl = 1; + } + + int32_t last = 0; + auto c = strsep(&p, "/"); + while (c) { + if (!d) { + return false; + } + + if (last) { + closedir(d); + return false; + } + + r[rl] = '/'; + rl += 1; + r[rl] = 0; + + struct dirent* e = readdir(d); + while (e) { + if (strcasecmp(c, e->d_name) == 0) { + strcpy(r + rl, e->d_name); + rl += strlen(e->d_name); + + closedir(d); + d = opendir(r); + + break; + } + + e = readdir(d); + } + + if (!e) { + strcpy(r + rl, c); + rl += strlen(c); + last = 1; + } + + c = strsep(&p, "/"); + } + + if (d) { + closedir(d); + } + + return true; +} + +#endif diff --git a/bc/file/path/Posix.hpp b/bc/file/path/Posix.hpp new file mode 100644 index 0000000..f4b0b3a --- /dev/null +++ b/bc/file/path/Posix.hpp @@ -0,0 +1,6 @@ +#ifndef BC_FILE_PATH_POSIX_HPP +#define BC_FILE_PATH_POSIX_HPP + +bool ResolvePosixCasePath(const char* path, char* r); + +#endif