From f5998d524a19ea6e369b2f0ae50475577dfd28c1 Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Wed, 26 Dec 2012 10:56:18 -0800 Subject: [PATCH 1/8] Add HOST_IS_CASE_SENSITIVE define --- Core/FileSystems/DirectoryFileSystem.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index 6b87c3f784..c187db572c 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -28,6 +28,26 @@ typedef void * HANDLE; #endif +#if defined(__APPLE__) + +#if TARGET_OS_IPHONE +#define HOST_IS_CASE_SENSITIVE true +#elif TARGET_IPHONE_SIMULATOR +#define HOST_IS_CASE_SENSITIVE false +#else +// Mac OSX case sensitivity defaults off, but is user configurable (when +// creating a filesytem), so assume the worst: +#define HOST_IS_CASE_SENSITIVE true +#endif + +#elif defined(_WIN32) || defined(__SYMBIAN32__) +#define HOST_IS_CASE_SENSITIVE false + +#else // Android, Linux, BSD (and the rest?) +#define HOST_IS_CASE_SENSITIVE true + +#endif + class DirectoryFileSystem : public IFileSystem { public: DirectoryFileSystem(IHandleAllocator *_hAlloc, std::string _basePath); From a0e1ab1181ed2eaa94b01c99aa2df34e671ca4ba Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 02:20:00 -0800 Subject: [PATCH 2/8] Reindent. --- Core/FileSystems/DirectoryFileSystem.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index aa7185f07c..6340d05fe5 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -128,8 +128,8 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) { entry.hFile = CreateFile(fullName.c_str(), desired, sharemode, 0, openmode, 0, 0); bool success = entry.hFile != INVALID_HANDLE_VALUE; #else - entry.hFile = fopen(fullName.c_str(), access & FILEACCESS_WRITE ? "wb" : "rb"); - bool success = entry.hFile != 0; + entry.hFile = fopen(fullName.c_str(), access & FILEACCESS_WRITE ? "wb" : "rb"); + bool success = entry.hFile != 0; #endif if (!success) { @@ -209,18 +209,18 @@ size_t DirectoryFileSystem::SeekFile(u32 handle, s32 position, FileMove type) { #ifdef _WIN32 DWORD moveMethod = 0; switch (type) { - case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break; - case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break; - case FILEMOVE_END: moveMethod = FILE_END; break; + case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break; + case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break; + case FILEMOVE_END: moveMethod = FILE_END; break; } DWORD newPos = SetFilePointer((*iter).second.hFile, (LONG)position, 0, moveMethod); return newPos; #else int moveMethod = 0; switch (type) { - case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break; - case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break; - case FILEMOVE_END: moveMethod = SEEK_END; break; + case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break; + case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break; + case FILEMOVE_END: moveMethod = SEEK_END; break; } fseek(iter->second.hFile, position, moveMethod); return ftell(iter->second.hFile); From 3839179a097d850bd9972c62f7d5cabb0a4cdc99 Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 03:24:21 -0800 Subject: [PATCH 3/8] Implement opening for append in DirectoryFileSystem. Add more non-Windows access modes. --- Core/FileSystems/DirectoryFileSystem.cpp | 30 +++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 6340d05fe5..e076061c87 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -106,6 +106,7 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) { OpenFileEntry entry; + //TODO: tests, should append seek to end of file? seeking in a file opened for append? #ifdef _WIN32 // Convert parameters to Windows permissions and access DWORD desired = 0; @@ -127,8 +128,35 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) { //Let's do it! entry.hFile = CreateFile(fullName.c_str(), 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 - entry.hFile = fopen(fullName.c_str(), access & FILEACCESS_WRITE ? "wb" : "rb"); + // Convert flags in access parameter to fopen access mode + const char *mode = NULL; + if (access & FILEACCESS_APPEND) { + if (access & FILEACCESS_READ) + mode = "ab+"; // append+read, create if needed + else + mode = "ab"; // append only, create if needed + } else if (access & FILEACCESS_WRITE) { + if (access & FILEACCESS_READ) { + // FILEACCESS_CREATE is ignored for read only, write only, and append + // because C++ standard fopen's nonexistant file creation can only be + // customized for files opened read+write + if (access & FILEACCESS_CREATE) + mode = "wb+"; // read+write, create if needed + else + mode = "rb+"; // read+write, but don't create + } else { + mode = "wb"; // write only, create if needed + } + } else { // neither write nor append, so default to read only + mode = "rb"; + } + + entry.hFile = fopen(fullName.c_str(), mode); bool success = entry.hFile != 0; #endif From 928150fbf4a49ad94a7df32541d0eebe0af3c2b1 Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 04:15:08 -0800 Subject: [PATCH 4/8] Add an ERROR_LOG for GetDirListing not implemented on non-Windows. --- Core/FileSystems/DirectoryFileSystem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index e076061c87..4e01590805 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -318,6 +318,8 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { if (!retval) break; } +#else + ERROR_LOG(HLE, "GetDirListing not implemented on non-Windows"); #endif return myVector; } From 725094eaefa003b95186e2fc7539bcad654b122e Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 04:23:04 -0800 Subject: [PATCH 5/8] Storing a size_t returned from std::string in an int and checking for negative is weird. --- Core/FileSystems/DirectoryFileSystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 4e01590805..7b648ead8f 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -78,8 +78,8 @@ bool DirectoryFileSystem::RenameFile(const std::string &from, const std::string std::string fullTo = to; // TO filename may not include path. Intention is that it uses FROM's path if (to.find("/") != std::string::npos) { - int offset = from.find_last_of("/"); - if (offset >= 0) { + size_t offset = from.find_last_of("/"); + if (offset != std::string::npos) { fullTo = from.substr(0, offset + 1) + to; } } From 9e85c01c1f03148beb85f0e92cfb9cb5d7ca78bb Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 04:27:07 -0800 Subject: [PATCH 6/8] Simulate case insensitivity on case sensitive platforms. --- Core/FileSystems/DirectoryFileSystem.cpp | 254 +++++++++++++++++++++-- Core/FileSystems/DirectoryFileSystem.h | 9 + 2 files changed, 245 insertions(+), 18 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 7b648ead8f..44d6dcfd2b 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -18,6 +18,7 @@ #ifdef _WIN32 #include #else +#include #include #include #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; diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index c187db572c..2de2ff9e2e 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -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 }; From 3862aea4deeb6b42b3a11b21031c71191b5b3cab Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 04:28:28 -0800 Subject: [PATCH 7/8] Minor comment edit. --- Core/FileSystems/DirectoryFileSystem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 44d6dcfd2b..fd7c4b8174 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -41,7 +41,7 @@ static bool FixFilenameCase(const std::string &path, std::string &filename) filename[i] = tolower(filename[i]); } - //TODO: lookup filename in cache for basePath + //TODO: lookup filename in cache for "path" struct dirent_large { struct dirent entry; char padding[FILENAME_MAX+1]; } diren; struct dirent_large; @@ -331,7 +331,7 @@ u32 DirectoryFileSystem::OpenFile(std::string filename, FileAccess access) { mode = "wb"; // write only, create if needed } } else { // neither write nor append, so default to read only - mode = "rb"; + mode = "rb"; // read only, don't create } entry.hFile = fopen(fullNameC, mode); From 1c5ea9903197d6ed8340f96656b082ca258cabd4 Mon Sep 17 00:00:00 2001 From: KentuckyCompass Date: Thu, 27 Dec 2012 05:14:06 -0800 Subject: [PATCH 8/8] Remove pointless string clear() --- Core/FileSystems/DirectoryFileSystem.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index fd7c4b8174..954dea960e 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -94,8 +94,6 @@ bool DirectoryFileSystem::FixPathCase(std::string &path, FixPathCaseBehavior beh std::string fullPath; fullPath.reserve(basePath.size() + len + 1); - - fullPath.clear(); fullPath.append(basePath); size_t start = 0;