mirror of
https://github.com/thunderbrewhq/bc.git
synced 2025-12-12 10:02:30 +00:00
feat(file): allow case-insensitive file paths to be resolved on Linux
This commit is contained in:
parent
58c8a50e8f
commit
3cfddf0149
6 changed files with 359 additions and 51 deletions
|
|
@ -3,6 +3,7 @@ file(GLOB BC_SOURCES
|
|||
"lock/*.cpp"
|
||||
"os/*.cpp"
|
||||
"file/*.cpp"
|
||||
"file/path/*.cpp"
|
||||
"time/*.cpp"
|
||||
"system/*.cpp"
|
||||
"system/file/*.cpp"
|
||||
|
|
|
|||
|
|
@ -1,51 +1 @@
|
|||
#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
|
||||
#include "bc/file/path/Path.hpp"
|
||||
|
|
|
|||
225
bc/file/path/Path.cpp
Normal file
225
bc/file/path/Path.cpp
Normal file
|
|
@ -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 <cctype>
|
||||
|
||||
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<char*>(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<char*>(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
|
||||
51
bc/file/path/Path.hpp
Normal file
51
bc/file/path/Path.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef BC_FILE_PATH_PATH_HPP
|
||||
#define BC_FILE_PATH_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* 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
|
||||
75
bc/file/path/Posix.cpp
Normal file
75
bc/file/path/Posix.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "bc/file/path/Posix.hpp"
|
||||
|
||||
#if defined(WHOA_SYSTEM_LINUX)
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <alloca.h>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
|
||||
// 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<char*>(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
|
||||
6
bc/file/path/Posix.hpp
Normal file
6
bc/file/path/Posix.hpp
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef BC_FILE_PATH_POSIX_HPP
|
||||
#define BC_FILE_PATH_POSIX_HPP
|
||||
|
||||
bool ResolvePosixCasePath(const char* path, char* r);
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue