Simulate case insensitivity on case sensitive platforms.

This commit is contained in:
KentuckyCompass 2012-12-27 04:27:07 -08:00
parent 725094eaef
commit 9e85c01c1f
2 changed files with 245 additions and 18 deletions

View file

@ -18,6 +18,7 @@
#ifdef _WIN32
#include <windows.h>
#else
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
@ -25,8 +26,105 @@
#include "FileUtil.h"
#include "DirectoryFileSystem.h"
// TODO: Simulate case insensitivity on Unix.
// NOTE: MacOSX is already case insensitive.
#if HOST_IS_CASE_SENSITIVE
static bool FixFilenameCase(const std::string &path, std::string &filename)
{
// Are we lucky?
if (File::Exists(path + filename))
return true;
size_t filenameSize = filename.size();
for (size_t i = 0; i < filenameSize; i++)
{
filename[i] = tolower(filename[i]);
}
//TODO: lookup filename in cache for basePath
struct dirent_large { struct dirent entry; char padding[FILENAME_MAX+1]; } diren;
struct dirent_large;
struct dirent *result = NULL;
DIR *dirp = opendir(path.c_str());
if (!dirp)
return false;
bool retValue = false;
while (!readdir_r(dirp, (dirent*) &diren, &result) && result)
{
if (result->d_namlen != filenameSize)
continue;
size_t i;
for (i = 0; i < filenameSize; i++)
{
if (filename[i] != tolower(result->d_name[i]))
break;
}
if (i < filenameSize)
continue;
filename = result->d_name;
retValue = true;
}
closedir(dirp);
return retValue;
}
bool DirectoryFileSystem::FixPathCase(std::string &path, FixPathCaseBehavior behavior)
{
size_t len = path.size();
if (len == 0)
return true;
if (path[len - 1] == '/')
{
len--;
if (len == 0)
return true;
}
std::string fullPath;
fullPath.reserve(basePath.size() + len + 1);
fullPath.clear();
fullPath.append(basePath);
size_t start = 0;
while (start < len)
{
size_t i = path.find('/', start);
if (i == std::string::npos)
i = len;
if (i > start)
{
std::string component = path.substr(start, i - start);
if (FixFilenameCase(fullPath, component) == false)
return (behavior == FPC_FILE_MUST_EXIST || (behavior == FPC_PATH_MUST_EXIST && i >= len));
path.replace(start, i - start, component);
fullPath.append(component);
fullPath.append(1, '/');
}
start = i + 1;
}
return true;
}
#endif
DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, std::string _basePath) : basePath(_basePath) {
File::CreateFullPath(basePath);
@ -60,11 +158,37 @@ std::string DirectoryFileSystem::GetLocalPath(std::string localpath) {
}
bool DirectoryFileSystem::MkDir(const std::string &dirname) {
#if HOST_IS_CASE_SENSITIVE
// Must fix case BEFORE attempting, because MkDir would create
// duplicate (different case) directories
std::string fixedCase = dirname;
if ( ! FixPathCase(fixedCase, FPC_PARTIAL_ALLOWED) )
return false;
return File::CreateFullPath(GetLocalPath(fixedCase));
#else
return File::CreateFullPath(GetLocalPath(dirname));
#endif
}
bool DirectoryFileSystem::RmDir(const std::string &dirname) {
std::string fullName = GetLocalPath(dirname);
#if HOST_IS_CASE_SENSITIVE
// Maybe we're lucky?
if (File::DeleteDirRecursively(fullName))
return true;
// Nope, fix case and try again
fullName = dirname;
if ( ! FixPathCase(fullName, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullName = GetLocalPath(fullName);
#endif
/*#ifdef _WIN32
return RemoveDirectory(fullName.c_str()) == TRUE;
#else
@ -74,7 +198,6 @@ bool DirectoryFileSystem::RmDir(const std::string &dirname) {
}
bool DirectoryFileSystem::RenameFile(const std::string &from, const std::string &to) {
std::string fullFrom = GetLocalPath(from);
std::string fullTo = to;
// TO filename may not include path. Intention is that it uses FROM's path
if (to.find("/") != std::string::npos) {
@ -83,26 +206,85 @@ bool DirectoryFileSystem::RenameFile(const std::string &from, const std::string
fullTo = from.substr(0, offset + 1) + to;
}
}
fullTo = GetLocalPath(fullTo);
#ifdef _WIN32
return MoveFile(fullFrom.c_str(), fullTo.c_str()) == TRUE;
#else
return 0 == rename(fullFrom.c_str(), fullTo.c_str());
std::string fullFrom = GetLocalPath(from);
#if HOST_IS_CASE_SENSITIVE
// In case TO should overwrite a file with different case
if ( ! FixPathCase(fullTo, FPC_PATH_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
#endif
fullTo = GetLocalPath(fullTo);
const char * fullToC = fullTo.c_str();
#ifdef _WIN32
bool retValue = (MoveFile(fullFrom.c_str(), fullToC) == TRUE);
#else
bool retValue = (0 == rename(fullFrom.c_str(), fullToC));
#endif
#if HOST_IS_CASE_SENSITIVE
if (! retValue)
{
// May have failed due to case sensitivity on FROM, so try again
fullFrom = from;
if ( ! FixPathCase(fullFrom, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullFrom = GetLocalPath(fullFrom);
#ifdef _WIN32
retValue = (MoveFile(fullFrom.c_str(), fullToC) == TRUE);
#else
retValue = (0 == rename(fullFrom.c_str(), fullToC));
#endif
}
#endif
return retValue;
}
bool DirectoryFileSystem::DeleteFile(const std::string &filename) {
std::string fullName = GetLocalPath(filename);
#ifdef _WIN32
return ::DeleteFile(fullName.c_str()) == TRUE;
bool retValue = (::DeleteFile(fullName.c_str()) == TRUE);
#else
return 0 == unlink(fullName.c_str());
bool retValue = (0 == unlink(fullName.c_str()));
#endif
#if HOST_IS_CASE_SENSITIVE
if (! retValue)
{
// May have failed due to case sensitivity, so try again
fullName = filename;
if ( ! FixPathCase(fullName, FPC_FILE_MUST_EXIST) )
return false; // or go on and attempt (for a better error code than just false?)
fullName = GetLocalPath(fullName);
#ifdef _WIN32
retValue = (::DeleteFile(fullName.c_str()) == TRUE);
#else
retValue = (0 == unlink(fullName.c_str()));
#endif
}
#endif
return retValue;
}
u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) {
#if HOST_IS_CASE_SENSITIVE
if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE))
{
DEBUG_LOG(HLE, "Checking case for path %s", filename.c_str());
if ( ! FixPathCase(filename, FPC_PATH_MUST_EXIST) )
return 0; // or go on and attempt (for a better error code than just 0?)
}
// else we try fopen first (in case we're lucky) before simulating case insensitivity
#endif
std::string fullName = GetLocalPath(filename);
INFO_LOG(HLE,"Actually opening %s (%s)", fullName.c_str(), filename.c_str());
const char *fullNameC = fullName.c_str();
INFO_LOG(HLE,"Actually opening %s (%s)", fullNameC, filename.c_str());
OpenFileEntry entry;
@ -126,12 +308,8 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) {
openmode = OPEN_EXISTING;
}
//Let's do it!
entry.hFile = CreateFile(fullName.c_str(), desired, sharemode, 0, openmode, 0, 0);
entry.hFile = CreateFile(fullNameC, desired, sharemode, 0, openmode, 0, 0);
bool success = entry.hFile != INVALID_HANDLE_VALUE;
if (success && (access & FILEACCESS_APPEND)) {
SetFilePointer(entry.hFile, 0, NULL, FILE_END);
}
#else
// Convert flags in access parameter to fopen access mode
const char *mode = NULL;
@ -156,17 +334,48 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) {
mode = "rb";
}
entry.hFile = fopen(fullName.c_str(), mode);
entry.hFile = fopen(fullNameC, mode);
bool success = entry.hFile != 0;
#endif
#if HOST_IS_CASE_SENSITIVE
if (!success &&
!(access & FILEACCESS_APPEND) &&
!(access & FILEACCESS_CREATE) &&
!(access & FILEACCESS_WRITE))
{
if ( ! FixPathCase(filename, FPC_PATH_MUST_EXIST) )
return 0; // or go on and attempt (for a better error code than just 0?)
fullName = GetLocalPath(filename);
fullNameC = fullName.c_str();
DEBUG_LOG(HLE, "Case may have been incorrect, second try opening %s (%s)", fullNameC, filename.c_str());
// And try again with the correct case this time
#ifdef _WIN32
entry.hFile = CreateFile(fullNameC, desired, sharemode, 0, openmode, 0, 0);
success = entry.hFile != INVALID_HANDLE_VALUE;
#else
entry.hFile = fopen(fullNameC, mode);
success = entry.hFile != 0;
#endif
}
#endif
if (!success) {
#ifdef _WIN32
ERROR_LOG(HLE, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i", GetLastError(), (int)access);
#else
ERROR_LOG(HLE, "DirectoryFileSystem::OpenFile: FAILED, access = %i", (int)access);
#endif
//wwwwaaaaahh!!
return 0;
} else {
#ifdef _WIN32
if (access & FILEACCESS_APPEND)
SetFilePointer(entry.hFile, 0, NULL, FILE_END);
#endif
u32 newHandle = hAlloc->GetNewHandle();
entries[newHandle] = entry;
@ -265,8 +474,17 @@ PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {
x.name = filename;
std::string fullName = GetLocalPath(filename);
if (!File::Exists(fullName)) {
if (! File::Exists(fullName)) {
#if HOST_IS_CASE_SENSITIVE
if (! FixPathCase(filename, FPC_FILE_MUST_EXIST))
return x;
fullName = GetLocalPath(filename);
if (! File::Exists(fullName))
return x;
#else
return x;
#endif
}
x.type = File::IsDirectory(fullName) ? FILETYPE_NORMAL : FILETYPE_DIRECTORY;
x.exists = true;

View file

@ -83,4 +83,13 @@ private:
// In case of Windows: Translate slashes, etc.
std::string GetLocalPath(std::string localpath);
#if HOST_IS_CASE_SENSITIVE
typedef enum {
FPC_FILE_MUST_EXIST, // all path components must exist (rmdir, move from)
FPC_PATH_MUST_EXIST, // all except the last one must exist - still tries to fix last one (fopen, move to)
FPC_PARTIAL_ALLOWED, // don't care how many exist (mkdir recursive)
} FixPathCaseBehavior;
bool FixPathCase(std::string &path, FixPathCaseBehavior behavior);
#endif
};