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"
|
"lock/*.cpp"
|
||||||
"os/*.cpp"
|
"os/*.cpp"
|
||||||
"file/*.cpp"
|
"file/*.cpp"
|
||||||
|
"file/path/*.cpp"
|
||||||
"time/*.cpp"
|
"time/*.cpp"
|
||||||
"system/*.cpp"
|
"system/*.cpp"
|
||||||
"system/file/*.cpp"
|
"system/file/*.cpp"
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1 @@
|
||||||
#ifndef BC_FILE_PATH_HPP
|
#include "bc/file/path/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
|
|
||||||
|
|
|
||||||
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