diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 30 | ||||
-rw-r--r-- | zen/com_error.h | 31 | ||||
-rw-r--r-- | zen/com_ptr.h | 2 | ||||
-rw-r--r-- | zen/dst_hack.cpp | 22 | ||||
-rw-r--r-- | zen/file_handling.cpp | 266 | ||||
-rw-r--r-- | zen/file_handling.h | 10 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 32 | ||||
-rw-r--r-- | zen/file_traverser.h | 5 | ||||
-rw-r--r-- | zen/i18n.h | 2 | ||||
-rw-r--r-- | zen/optional.h | 2 | ||||
-rw-r--r-- | zen/osx_error.h | 27 | ||||
-rw-r--r-- | zen/osx_string.h | 82 | ||||
-rw-r--r-- | zen/osx_throw_exception.h | 56 | ||||
-rw-r--r-- | zen/privilege.cpp | 2 | ||||
-rw-r--r-- | zen/recycler.cpp | 1 | ||||
-rw-r--r-- | zen/scope_guard.h | 10 | ||||
-rw-r--r-- | zen/thread.h | 2 | ||||
-rw-r--r-- | zen/zstring.cpp | 12 | ||||
-rw-r--r-- | zen/zstring.h | 12 |
19 files changed, 407 insertions, 199 deletions
diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 0691ac5b..b3990ee0 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -166,14 +166,14 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError void* sink) { ComPtr<IFileOperation> fileOp; - ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError + ZEN_COM_CHECK(::CoCreateInstance(CLSID_FileOperation, //throw ComError nullptr, CLSCTX_ALL, IID_PPV_ARGS(fileOp.init()))); // Set the operation flags. Turn off all UI from being shown to the user during the // operation. This includes error, confirmation and progress dialogs. - ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | + ZEN_COM_CHECK(fileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | //no progress dialog box FOF_NOERRORUI | @@ -192,7 +192,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError throw ComError(L"Error creating RecyclerProgressCallback.", E_OUTOFMEMORY); DWORD callbackID = 0; - ZEN_CHECK_COM(fileOp->Advise(opProgress.get(), &callbackID)); + ZEN_COM_CHECK(fileOp->Advise(opProgress.get(), &callbackID)); ZEN_ON_SCOPE_EXIT(fileOp->Unadvise(callbackID)); //RecyclerProgressCallback might outlive current scope, so cut access to "callback, sink" int operationCount = 0; @@ -226,7 +226,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError throw ComError(std::wstring(L"Error calling \"SHCreateItemFromParsingName\" for file:\n") + L"\'" + fileNames[i] + L"\'.", hr); } - ZEN_CHECK_COM(fileOp->DeleteItem(psiFile.get(), nullptr)); + ZEN_COM_CHECK(fileOp->DeleteItem(psiFile.get(), nullptr)); ++operationCount; } @@ -237,7 +237,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError //perform planned operations try { - ZEN_CHECK_COM(fileOp->PerformOperations()); + ZEN_COM_CHECK(fileOp->PerformOperations()); } catch (const ComError&) { @@ -263,7 +263,7 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError //if FOF_NOERRORUI without FOFX_EARLYFAILURE is set, PerformOperations() can return with success despite errors, but sets the following "aborted" flag instead BOOL pfAnyOperationsAborted = FALSE; - ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); + ZEN_COM_CHECK(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); if (pfAnyOperationsAborted == TRUE) throw ComError(L"Operation did not complete successfully."); @@ -274,7 +274,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError const wchar_t* targetFile) { ComPtr<IFileOperation> fileOp; - ZEN_CHECK_COM(::CoCreateInstance(CLSID_FileOperation, //throw ComError + ZEN_COM_CHECK(::CoCreateInstance(CLSID_FileOperation, //throw ComError nullptr, CLSCTX_ALL, IID_PPV_ARGS(fileOp.init()))); @@ -283,7 +283,7 @@ void copyFile(const wchar_t* sourceFile, //throw ComError // from being shown to the user during the // operation. This includes error, confirmation // and progress dialogs. - ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_NOCONFIRMATION | //throw ComError + ZEN_COM_CHECK(fileOp->SetOperationFlags(FOF_NOCONFIRMATION | //throw ComError FOF_SILENT | FOFX_EARLYFAILURE | FOF_NOERRORUI)); @@ -315,14 +315,14 @@ void copyFile(const wchar_t* sourceFile, //throw ComError } //schedule file copy operation - ZEN_CHECK_COM(fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), nullptr)); + ZEN_COM_CHECK(fileOp->CopyItem(psiSourceFile.get(), psiTargetFolder.get(), targetFileNameShort.c_str(), nullptr)); //perform actual operations - ZEN_CHECK_COM(fileOp->PerformOperations()); + ZEN_COM_CHECK(fileOp->PerformOperations()); //check if errors occured: if FOFX_EARLYFAILURE is not used, PerformOperations() can return with success despite errors! BOOL pfAnyOperationsAborted = FALSE; - ZEN_CHECK_COM(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); + ZEN_COM_CHECK(fileOp->GetAnyOperationsAborted(&pfAnyOperationsAborted)); if (pfAnyOperationsAborted == TRUE) throw ComError(L"Operation did not complete successfully."); @@ -332,10 +332,10 @@ void copyFile(const wchar_t* sourceFile, //throw ComError void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError { ComPtr<IShellFolder> desktopFolder; - ZEN_CHECK_COM(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError + ZEN_COM_CHECK(::SHGetDesktopFolder(desktopFolder.init())); //throw ComError PIDLIST_RELATIVE pidlFolder = nullptr; - ZEN_CHECK_COM(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, + ZEN_COM_CHECK(desktopFolder->ParseDisplayName(nullptr, // [in] HWND hwnd, nullptr, // [in] IBindCtx *pbc, const_cast<LPWSTR>(dirname), // [in] LPWSTR pszDisplayName, nullptr, // [out] ULONG *pchEaten, @@ -344,11 +344,11 @@ void getFolderClsid(const wchar_t* dirname, CLSID& pathCLSID) //throw ComError ZEN_ON_SCOPE_EXIT(::ILFree(pidlFolder)); //older version: ::CoTaskMemFree ComPtr<IPersist> persistFolder; - ZEN_CHECK_COM(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl, + ZEN_COM_CHECK(desktopFolder->BindToObject(pidlFolder, // [in] PCUIDLIST_RELATIVE pidl, nullptr, // [in] IBindCtx *pbc, IID_PPV_ARGS(persistFolder.init()))); //throw ComError - ZEN_CHECK_COM(persistFolder->GetClassID(&pathCLSID)); //throw ComError + ZEN_COM_CHECK(persistFolder->GetClassID(&pathCLSID)); //throw ComError } diff --git a/zen/com_error.h b/zen/com_error.h index e6f5b492..0e0448a7 100644 --- a/zen/com_error.h +++ b/zen/com_error.h @@ -26,10 +26,11 @@ private: std::wstring msg_; }; -#define ZEN_CHECK_COM(func) ZEN_CHECK_COM_ERROR(func, #func) //throw ComError -/*Convenience Macro checking for COM errors: +//Convenience Macros checking for COM errors: -Example: ZEN_CHECK_COM(backupComp->InitializeForBackup()); +#define ZEN_COM_CHECK(func) ZEN_COM_CHECK_IMPL(func, #func) //throw ComError +/* +Example: ZEN_COM_CHECK(backupComp->InitializeForBackup()); Equivalent to: { @@ -39,6 +40,15 @@ Equivalent to: } */ +#define ZEN_COM_ASSERT(obj) ZEN_COM_ASSERT_IMPL(obj, #obj) //throw ComError +/* +Example: ZEN_COM_ASSERT(obj); + +Equivalent to: + if (!obj) + throw ComError(L"Assertion failed: \"obj\".", E_FAIL); +*/ + @@ -218,16 +228,13 @@ std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr) } -#define ZEN_CHECK_COM_ERROR(func, txt) \ - { \ - HRESULT hrInternal = func; \ - if (FAILED(hrInternal)) \ - throw ComError(L"Error calling \"" ## ZEN_CONCAT_SUB(L, txt) ## L"\".", hrInternal); \ +#define ZEN_COM_CHECK_IMPL(func, txt) \ + { \ + HRESULT hrInternal = func; \ + if (FAILED(hrInternal)) \ + throw zen::ComError(std::wstring(L"Error calling \"") + L ## txt + L"\".", hrInternal); \ } -#ifndef ZEN_CONCAT //redeclare those macros: avoid dependency to scope_guard.h -#define ZEN_CONCAT_SUB(X, Y) X ## Y -#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y) -#endif +#define ZEN_COM_ASSERT_IMPL(obj, txt) if (!(obj)) throw zen::ComError(std::wstring(L"Assertion failed: \"") + L ## txt + L"\".", E_FAIL); } #endif //COM_ERROR_HEADER diff --git a/zen/com_ptr.h b/zen/com_ptr.h index 030a0801..9944ea56 100644 --- a/zen/com_ptr.h +++ b/zen/com_ptr.h @@ -36,7 +36,7 @@ class ComPtr public: ComPtr() : ptr(nullptr) {} // ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } //noexcept in C++11 - ComPtr( ComPtr&& other) : ptr(other.ptr) { other.ptr = nullptr; } // + ComPtr( ComPtr&& other) : ptr(other.release()) {} // ~ComPtr() { if (ptr) ptr->Release(); } //has exception spec of compiler-generated destructor by default ComPtr& operator=(const ComPtr& other) { ComPtr(other).swap(*this); return *this; } //noexcept in C++11 diff --git a/zen/dst_hack.cpp b/zen/dst_hack.cpp index 6e5c2230..a70ef13b 100644 --- a/zen/dst_hack.cpp +++ b/zen/dst_hack.cpp @@ -52,22 +52,22 @@ Zstring getVolumeName(const Zstring& filename) bool dst::isFatDrive(const Zstring& fileName) //throw() { - const size_t BUFFER_SIZE = MAX_PATH + 1; - wchar_t fsName[BUFFER_SIZE]; - const Zstring volumePath = getVolumeName(fileName); if (volumePath.empty()) return false; + const DWORD bufferSize = MAX_PATH + 1; + wchar_t fsName[bufferSize]; + //suprisingly fast: ca. 0.03 ms per call! - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - fsName, //__out LPTSTR lpFileSystemNameBuffer, - BUFFER_SIZE)) //__in DWORD nFileSystemNameSize + if (!::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName, + nullptr, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + nullptr, //__out_opt LPDWORD lpFileSystemFlags, + fsName, //__out LPTSTR lpFileSystemNameBuffer, + bufferSize)) //__in DWORD nFileSystemNameSize { assert(false); //shouldn't happen return false; diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index bf829010..3565700a 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -40,7 +40,7 @@ #if defined FFS_LINUX || defined FFS_MAC #include <sys/stat.h> -//#include <sys/time.h> +//#include <sys/time.h> #endif using namespace zen; @@ -250,26 +250,24 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! { //note: this even works for network shares: \\share\dirname - const DWORD BUFFER_SIZE = 10000; - std::vector<wchar_t> buffer(BUFFER_SIZE); + const DWORD bufferSize = 10000; + std::vector<wchar_t> buffer(bufferSize); //full pathName need not yet exist! if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, &buffer[0], //__out LPTSTR lpszVolumePathName, - BUFFER_SIZE)) //__in DWORD cchBufferLength + bufferSize)) //__in DWORD cchBufferLength return 0; - Zstring volumePath = appendSeparator(&buffer[0]); - DWORD volumeSerial = 0; - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize + if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName, + nullptr, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + nullptr, //__out_opt LPDWORD lpFileSystemFlags, + nullptr, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize return 0; return volumeSerial; @@ -376,7 +374,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File { DWORD lastError = ::GetLastError(); - const std::wstring shortMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", fmtFileName(oldName)), L"%y", fmtFileName(newName)); + const std::wstring shortMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName)); if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! lastError == ERROR_LOCK_VIOLATION) @@ -429,7 +427,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File if (::rename(oldName.c_str(), newName.c_str()) != 0) { const int lastError = errno; - std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", fmtFileName(oldName)), L"%y", fmtFileName(newName)) + + std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName)) + L"\n\n" + getLastErrorFormatted(lastError); if (lastError == EXDEV) @@ -513,7 +511,7 @@ bool have8dot3NameClash(const Zstring& filename) return false; } -class Fix8Dot3NameClash +class Fix8Dot3NameClash //throw FileError { public: Fix8Dot3NameClash(const Zstring& filename) @@ -560,7 +558,7 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil //try to handle issues with already existing short 8.3 file names on Windows if (have8dot3NameClash(newName)) { - Fix8Dot3NameClash dummy(newName); //move clashing filename to the side + Fix8Dot3NameClash dummy(newName); //throw FileError; move clashing filename to the side //now try again... renameFile_sub(oldName, newName); //throw FileError return; @@ -1259,11 +1257,96 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym } -void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing +void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTargetExisting +{ + assert(!endsWith(directory, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! + + try + { + makeDirectoryPlain(directory, Zstring(), false); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing + } + catch (const ErrorTargetPathMissing&) + { + //we need to create parent directories first + const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR); + if (!dirParent.empty()) + { + //recurse... + try + { + makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting) + } + catch (const ErrorTargetExisting& e) { throw FileError(e.toString()); } + //yes it's pathological, but we do not want to emit ErrorTargetExisting when creating parent directories! + + //now try again... + makeDirectoryPlain(directory, Zstring(), false); //throw FileError, ErrorTargetExisting, (ErrorTargetPathMissing) + return; + } + throw; + } +} +} + + +void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw FileError, ErrorTargetExisting +{ + //remove trailing separator (even for C:\ root directories) + const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? + beforeLast(directory, FILE_NAME_SEPARATOR) : + directory; + + try + { + makeDirectoryRecursively(dirFormatted); //FileError, ErrorTargetExisting + } + catch (const ErrorTargetExisting&) + { + //avoid any file system race-condition by *not* checking directory existence again here!!! + if (failIfExists) + throw; + } + catch (const FileError&) + { + if (dirExists(directory)) //a file system race-condition! + { + /* + could there be situations where a directory/network path exists, + but creation fails with error different than "ErrorTargetExisting"?? + - creation of C:\ fails with ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS + */ + assert(false); + if (failIfExists) + throw; //do NOT convert to ErrorTargetExisting: if "failIfExists", not getting a ErrorTargetExisting *atomically* is unexpected! + } + else + throw; + } +} + + +void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing const Zstring& templateDir, bool copyFilePermissions) { #ifdef FFS_WIN + //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! + Zstring dirTmp = removeLongPathPrefix(endsWith(directory, FILE_NAME_SEPARATOR) ? + beforeLast(directory, FILE_NAME_SEPARATOR) : + directory); + if (dirTmp.size() == 2 && + std::iswalpha(dirTmp[0]) && dirTmp[1] == L':') + { + dirTmp += FILE_NAME_SEPARATOR; //we do not support "C:" to represent a relative path! + + const ErrorCode lastError = dirExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; + + const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(dirTmp)) + L"\n\n" + getLastErrorFormatted(lastError); + if (lastError == ERROR_ALREADY_EXISTS) + throw ErrorTargetExisting(msg); + throw FileError(msg); //[!] this is NOT a ErrorTargetPathMissing case! + } + //don't use ::CreateDirectoryEx: //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! @@ -1271,14 +1354,30 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), //__in LPCTSTR lpPathName, nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes { - const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(); - const ErrorCode lastError = getLastError(); + ErrorCode lastError = getLastError(); + //handle issues with already existing short 8.3 file names on Windows if (lastError == ERROR_ALREADY_EXISTS) - throw ErrorTargetExisting(msg); - else if (lastError == ERROR_PATH_NOT_FOUND) - throw ErrorTargetPathMissing(msg); - throw FileError(msg); + if (have8dot3NameClash(directory)) + { + Fix8Dot3NameClash dummy(directory); //throw FileError; move clashing object to the side + + //now try again... + if (::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), nullptr)) + lastError = ERROR_SUCCESS; + else + lastError = getLastError(); + } + + if (lastError != ERROR_SUCCESS) + { + const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError); + if (lastError == ERROR_ALREADY_EXISTS) + throw ErrorTargetExisting(msg); + else if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(msg); + throw FileError(msg); + } } #elif defined FFS_LINUX || defined FFS_MAC @@ -1301,19 +1400,17 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT //try to copy file attributes Zstring sourcePath; - if (symlinkExists(templateDir)) //dereference symlink! - { + if (symlinkExists(templateDir)) try { //get target directory of symbolic link sourcePath = getSymlinkTargetPath(templateDir); //throw FileError } catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... - } - else //no symbolic link + else sourcePath = templateDir; - //try to copy file attributes + //*try* to copy file attributes if (!sourcePath.empty()) { const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str()); @@ -1368,95 +1465,6 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT } -void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting -{ - try - { - createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing - } - catch (const ErrorTargetExisting&) - { -#ifdef FFS_WIN - //handle issues with already existing short 8.3 file names on Windows - if (have8dot3NameClash(directory)) - { - Fix8Dot3NameClash dummy(directory); //move clashing object to the side - - //now try again... - createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing - return; - } -#endif - throw; - } - catch (const ErrorTargetPathMissing&) - { - //we need to create parent directories first - const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR); - if (!dirParent.empty()) - { - //call function recursively - const Zstring templateParent = beforeLast(templateDir, FILE_NAME_SEPARATOR); //returns empty string if ch not found - createDirectoryRecursively(dirParent, templateParent, copyFilePermissions); //throw - - //now try again... - createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing - return; - } - throw; - } -} -} - - -void zen::makeNewDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting -{ -#ifdef FFS_WIN - //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! - const Zstring dirTmp = removeLongPathPrefix(directory); - if (dirTmp.size() == 3 && - std::iswalpha(dirTmp[0]) && endsWith(dirTmp, L":\\")) - { - const ErrorCode lastError = dirExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; - - const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(dirTmp)) + L"\n\n" + getLastErrorFormatted(lastError); - if (lastError == ERROR_ALREADY_EXISTS) - throw ErrorTargetExisting(msg); - throw FileError(msg); - } -#endif - //remove trailing separator (except for volume root directories!) - const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? - beforeLast(directory, FILE_NAME_SEPARATOR) : - directory; - - const Zstring templateFormatted = endsWith(templateDir, FILE_NAME_SEPARATOR) ? - beforeLast(templateDir, FILE_NAME_SEPARATOR) : - templateDir; - - createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions); //FileError, ErrorTargetExisting -} - - -void zen::makeDirectory(const Zstring& directory) -{ - try - { - makeNewDirectory(directory, Zstring(), false); //FileError, ErrorTargetExisting - } - catch (const FileError& e) - { - assert(dynamic_cast<const ErrorTargetExisting*>(&e)); - (void)e; - //could there be situations where a directory/network path exists, but creation fails with - //error different than "ErrorTargetExisting"?? => better catch all "FileError" and check existence again - if (dirExists(directory)) //technically a file system race-condition! - return; - throw; - } -} - - void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError { const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw FileError @@ -1481,7 +1489,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool #elif defined FFS_LINUX || defined FFS_MAC if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) #endif - throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", fmtFileName(sourceLink)), L"%y", fmtFileName(targetLink)) + + throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)) + L"\n\n" + getLastErrorFormatted()); //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist @@ -1996,15 +2004,8 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE! //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart && dwStreamNumber == 1) {} - if (cbd.userCallback) - { - //some odd check for some possible(?) error condition - if (totalBytesTransferred.QuadPart < 0) //let's see if someone answers the call... - ::MessageBox(nullptr, L"You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\ - Please write a mail to the author of FreeFileSync at zenju@gmx.de and simply state that\n\ - \"totalBytesTransferred.HighPart can be below zero\"!\n\n\ - This will then be handled in future versions of FreeFileSync.\n\nThanks -Zenju", - nullptr, 0); + if (cbd.userCallback && + totalBytesTransferred.QuadPart >= 0) //should be always true, but let's still check try { cbd.userCallback->updateCopyStatus(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X! @@ -2017,13 +2018,12 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, cbd.errorHandler.reportUserException(*cbd.userCallback); return PROGRESS_CANCEL; } - } return PROGRESS_CONTINUE; } const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 -//const bool supportUnbufferedCopy = vistaOrLater(); +//const bool supportUnbufferedCopy = vistaOrLater(); //caveat: function scope static initialization is not thread-safe in VS 2010! @@ -2074,7 +2074,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile, throw ErrorShouldCopyAsSparse(L"sparse dummy value2"); //assemble error message... - std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", fmtFileName(sourceFile)), L"%y", fmtFileName(targetFile)) + + std::wstring errorMessage = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted(lastError); //if file is locked throw "ErrorFileLocked" instead! @@ -2162,7 +2162,7 @@ void copyFileWindows(const Zstring& sourceFile, const Zstring& targetFile, Callb //try to handle issues with already existing short 8.3 file names on Windows if (have8dot3NameClash(targetFile)) { - Fix8Dot3NameClash dummy(targetFile); //move clashing filename to the side + Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing filename to the side copyFileWindowsSelectRoutine(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now return; } diff --git a/zen/file_handling.h b/zen/file_handling.h index 5739dc2a..a0bd9b5b 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -55,9 +55,13 @@ void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileErr bool supportsPermissions(const Zstring& dirname); //throw FileError, derefernces symlinks -//creates superdirectories automatically: -void makeDirectory(const Zstring& directory); //throw FileError; do nothing if directory already exists! -void makeNewDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError, ErrorTargetExisting +//if parent directory not existing: create recursively: +void makeDirectory(const Zstring& directory, bool failIfExists = false); //throw FileError, ErrorTargetExisting + +//fail if already existing or parent not existing: +//directory should not end with path separator +//templateDir may be empty +void makeDirectoryPlain(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing struct FileAttrib { diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index b39f8416..7093c44a 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -17,7 +17,11 @@ #include "dll.h" #include "FindFilePlus/find_file_plus.h" -#elif defined FFS_LINUX || defined FFS_MAC +#elif defined FFS_MAC +#include <zen/osx_string.h> +#endif + +#if defined FFS_LINUX || defined FFS_MAC #include <sys/stat.h> #include <dirent.h> #endif @@ -284,7 +288,7 @@ struct FilePlusTraverser */ if (lastError == ERROR_NOT_SUPPORTED) { - fb(); //fallback should apply to whole directory sub-tree! + fb(); //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications! return false; } @@ -555,11 +559,30 @@ private: return; //don't return "." and ".." - const char* const shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + const char* shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; +#ifdef FFS_MAC + //some file system abstraction layers fail to properly return decomposed UTF8: http://developer.apple.com/library/mac/#qa/qa1173/_index.html + //so we need to do it ourselves; perf: ~600 ns per conversion + //note: it's not sufficient to apply this in z_impl::compareFilenamesNoCase: if UTF8 forms differ, FFS assumes a rename in case sensitivity and + // will try to propagate the rename => this won't work if target drive reports a particular UTF8 form only! + if (CFStringRef cfStr = osx::createCFString(shortName)) + { + ZEN_ON_SCOPE_EXIT(::CFRelease(cfStr)); + CFIndex lenMax = ::CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr); //"could be much larger than the actual space required" => don't store in Zstring + if (lenMax > 0) + { + bufferUtfDecomposed.resize(lenMax); + if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation + shortName = &bufferUtfDecomposed[0]; + } + } + //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; + //const char* samplePrecomposed = "\xc3\xb3.txt"; +#endif const Zstring& fullName = appendSeparator(directory) + shortName; struct ::stat statData = {}; @@ -645,6 +668,9 @@ private: } std::vector<char> buffer; +#ifdef FFS_MAC + std::vector<char> bufferUtfDecomposed; +#endif }; #endif } diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 7e566075..97fb0e9f 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -62,12 +62,13 @@ struct DstHackCallback virtual void requestUiRefresh(const Zstring& filename) = 0; //applying DST hack imposes significant one-time performance drawback => callback to inform user }; #elif defined FFS_LINUX || defined FFS_MAC -struct DstHackCallback; //DST hack not required on Linux +struct DstHackCallback; //DST hack not required on Unix #endif //custom traverser with detail information about files +//Win: client needs to handle duplicate file notifications! (FilePlusTraverser fallback) //directory may end with PATH_SEPARATOR -void traverseFolder(const Zstring& directory, //throw(); +void traverseFolder(const Zstring& directory, //throw() TraverseCallback& sink, DstHackCallback* dstCallback = nullptr); //apply DST hack if callback is supplied } @@ -22,7 +22,7 @@ namespace zen { -//implement handler to enable program wide localizations +//implement handler to enable program wide localizations: implement THREAD-SAFE ACCESS! struct TranslationHandler { virtual ~TranslationHandler() {} diff --git a/zen/optional.h b/zen/optional.h index 4d85e53a..a6a53103 100644 --- a/zen/optional.h +++ b/zen/optional.h @@ -10,7 +10,7 @@ namespace zen { /* -Optional return value with static memory allocation! +Optional return value without heap memory allocation! -> interface like a pointer, performance like a value Usage: diff --git a/zen/osx_error.h b/zen/osx_error.h new file mode 100644 index 00000000..4b0aeb3b --- /dev/null +++ b/zen/osx_error.h @@ -0,0 +1,27 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_ERRROR_834270598342753425 +#define OSX_ERRROR_834270598342753425 + +#include <string> + +namespace osx +{ +class OsxError //Exception base class used to notify file/directory copy/delete errors +{ +public: + explicit OsxError(const std::wstring& message) : msg(message) {} + virtual ~OsxError() {} + + const std::wstring& toString() const { return msg; } + +private: + std::wstring msg; +}; +} + +#endif //OSX_ERRROR_834270598342753425 diff --git a/zen/osx_string.h b/zen/osx_string.h new file mode 100644 index 00000000..a5c0849e --- /dev/null +++ b/zen/osx_string.h @@ -0,0 +1,82 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_STRING_1873641732143214324 +#define OSX_STRING_1873641732143214324 + +#include <zen/zstring.h> +#include <CoreFoundation/CoreFoundation.h> //CFString + +namespace osx +{ +Zstring cfStringToZstring(const CFStringRef& cfStr); + +CFStringRef createCFString (const char* utf8Str); //returns nullptr on error +CFMutableStringRef createMutableCFString(const char* utf8Str); //pass ownership! => ZEN_ON_SCOPE_EXIT(::CFRelease(utf8Str)); + + + + + + + + + + + + + +//################# implementation ##################### +inline +Zstring cfStringToZstring(const CFStringRef& cfStr) +{ + if (cfStr) + { + //perf: try to get away cheap: + if (const char* utf8Str = ::CFStringGetCStringPtr(cfStr, kCFStringEncodingUTF8)) + return utf8Str; + + CFIndex length = ::CFStringGetLength(cfStr); + if (length > 0) + { + CFIndex bufferSize = ::CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); + Zstring buffer; + buffer.resize(bufferSize); + + if (::CFStringGetCString(cfStr, &*buffer.begin(), bufferSize, kCFStringEncodingUTF8)) + { + buffer.resize(zen::strLength(&*buffer.begin())); //caveat: memory consumption of returned string! + return buffer; + } + } + } + return Zstring(); +} + + +inline +CFStringRef createCFString(const char* utf8Str) +{ + //don't bother with CFStringCreateWithBytes: it's slightly slower, despite passing length info + return ::CFStringCreateWithCString(nullptr, //CFAllocatorRef alloc, + utf8Str, //const char *cStr, + kCFStringEncodingUTF8); //CFStringEncoding encoding +} + + +inline +CFMutableStringRef createMutableCFString(const char* utf8Str) +{ + if (CFMutableStringRef strRef = ::CFStringCreateMutable(NULL, 0)) + { + ::CFStringAppendCString(strRef, utf8Str, kCFStringEncodingUTF8); + return strRef; + } + return nullptr; +} +} + +#endif //OSX_STRING_1873641732143214324 diff --git a/zen/osx_throw_exception.h b/zen/osx_throw_exception.h new file mode 100644 index 00000000..cb458974 --- /dev/null +++ b/zen/osx_throw_exception.h @@ -0,0 +1,56 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef OSX_EXCEPTION_89274305834255 +#define OSX_EXCEPTION_89274305834255 + +#import <Cocoa/Cocoa.h> +#include <zen/osx_error.h> +#include <zen/utf.h> + +namespace osx +{ +//for use in Objective C implementation files only! +void throwOsxError(NSException* e); //throw OsxError + +#define ZEN_OSX_ASSERT(obj) ZEN_OSX_ASSERT_IMPL(obj, #obj) //throw OsxError +/* +Example: ZEN_COM_ASSERT(obj); + +Equivalent to: + if (!obj) + throw OsxError(L"Assertion failed: \"obj\"."); +*/ + + + + + + +//######################## implmentation ############################ +inline +void throwOsxError(NSException* e) //throw OsxError +{ + std::string msg; + if (const char* name = [[e name ] cStringUsingEncoding:NSUTF8StringEncoding]) //"const char*" NOT owned by us! + msg += name; + if (const char* descr = [[e reason] cStringUsingEncoding:NSUTF8StringEncoding]) + { + msg += "\n"; + msg += descr; + } + throw OsxError(zen::utfCvrtTo<std::wstring>(msg)); + /* + e.g. + NSInvalidArgumentException + *** +[NSString stringWithCString:encoding:]: NULL cString + */ +} +} + +#define ZEN_OSX_ASSERT_IMPL(obj, txt) if (!(obj)) throw osx::OsxError(std::wstring(L"Assertion failed: \"") + L ## txt + L"\"."); + +#endif //OSX_EXCEPTION_89274305834255 diff --git a/zen/privilege.cpp b/zen/privilege.cpp index 288a1480..b474958e 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -99,7 +99,7 @@ public: private: Privileges() {} Privileges(Privileges&); - void operator=(Privileges&); + Privileges& operator=(const Privileges&); ~Privileges() //clean up: deactivate all privileges that have been activated by this application { diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 2a82cd24..c062a26c 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -13,7 +13,6 @@ //#include <algorithm> //#include <functional> #include <zen/dll.h> -#include <zen/win.h> //includes "windows.h" #include <zen/assert_static.h> #include <zen/win_ver.h> #include <zen/long_path_prefix.h> diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 81f47f87..ca05d39d 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -15,7 +15,7 @@ namespace zen { //Scope Guard /* - zen::ScopeGuard lockAio = zen::makeGuard([&]() { ::CancelIo(hDir); }); + zen::ScopeGuard lockAio = zen::makeGuard([&] { ::CancelIo(hDir); }); ... lockAio.dismiss(); */ @@ -33,13 +33,13 @@ public: protected: ScopeGuardBase() : dismissed_(false) {} ScopeGuardBase(ScopeGuardBase&& other) : dismissed_(other.dismissed_) { other.dismiss(); } //take over responsibility - ~ScopeGuardBase() {} + ~ScopeGuardBase() {} //[!] protected non-virtual base class destructor bool isDismissed() const { return dismissed_; } private: - ScopeGuardBase(const ScopeGuardBase&); //delete - ScopeGuardBase& operator=(const ScopeGuardBase&); // = delete; + ScopeGuardBase (const ScopeGuardBase&); // = delete + ScopeGuardBase& operator=(const ScopeGuardBase&); // bool dismissed_; }; @@ -76,6 +76,6 @@ ScopeGuardImpl<typename std::decay<F>::type> makeGuard(F&& fun) { return ScopeGu #define ZEN_CONCAT_SUB(X, Y) X ## Y #define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y) -#define ZEN_ON_SCOPE_EXIT(X) zen::ScopeGuard ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__); +#define ZEN_ON_SCOPE_EXIT(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__); #endif //ZEN_SCOPEGUARD_8971632487321434 diff --git a/zen/thread.h b/zen/thread.h index ae865cc8..db9cf3a3 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -7,7 +7,7 @@ #ifndef BOOST_THREAD_WRAP_H #define BOOST_THREAD_WRAP_H -//temporary solution until C++11 thread becomes fully available +//temporary solution until C++11 thread becomes fully available (considering std::thread's non-interruptibility and std::async craziness, this may be NEVER) #include <memory> //fix this pathetic boost thread warning mess diff --git a/zen/zstring.cpp b/zen/zstring.cpp index b371e598..262df49e 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -12,6 +12,7 @@ #include "win_ver.h" #elif defined FFS_MAC +//#include <zen/scope_guard.h> #include <ctype.h> //toupper() #endif @@ -132,7 +133,7 @@ time per call | function #ifdef FFS_WIN namespace { -#ifndef LOCALE_INVARIANT //not known to MinGW +#ifdef __MINGW32__ //MinGW is clueless... #define LOCALE_INVARIANT 0x007f #endif @@ -169,7 +170,7 @@ int z_impl::compareFilenamesNoCase(const wchar_t* lhs, const wchar_t* rhs, size_ } else //fallback { - //do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!! + //do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!! //the only reliable way to compare filenames (with XP) is to call "CharUpper" or "LCMapString": const auto minSize = static_cast<unsigned int>(std::min(sizeLhs, sizeRhs)); @@ -229,9 +230,16 @@ void z_impl::makeFilenameUpperCase(wchar_t* str, size_t size) } #elif defined FFS_MAC +int z_impl::compareFilenamesNoCase(const char* lhs, const char* rhs, size_t sizeLhs, size_t sizeRhs) +{ + return ::strcasecmp(lhs, rhs); //locale-dependent! +} + + void z_impl::makeFilenameUpperCase(char* str, size_t size) { std::for_each(str, str + size, [](char& c) { c = static_cast<char>(::toupper(static_cast<unsigned char>(c))); }); //locale-dependent! //result of toupper() is an unsigned char mapped to int range, so the char representation is in the last 8 bits and we need not care about signedness! + //this should work for UTF-8, too: all chars >= 128 are mapped upon themselves! } #endif diff --git a/zen/zstring.h b/zen/zstring.h index f4a79181..435f03a2 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -11,7 +11,7 @@ #ifdef FFS_LINUX #include <cstring> //strcmp #elif defined FFS_MAC -#include <strings.h> //strcasecmp +//#include <strings.h> //strcasecmp #endif @@ -101,10 +101,8 @@ Zstring appendSeparator(Zstring path) //support rvalue references! //################################# inline implementation ######################################## namespace z_impl { -#if defined FFS_WIN -int compareFilenamesNoCase(const Zchar* lhs, const Zchar* rhs, size_t sizeLhs, size_t sizeRhs); -#endif #if defined FFS_WIN || defined FFS_MAC +int compareFilenamesNoCase(const Zchar* lhs, const Zchar* rhs, size_t sizeLhs, size_t sizeRhs); void makeFilenameUpperCase(Zchar* str, size_t size); #endif } @@ -113,12 +111,12 @@ void makeFilenameUpperCase(Zchar* str, size_t size); template <template <class, class> class SP, class AP> inline int cmpFileName(const zen::Zbase<Zchar, SP, AP>& lhs, const zen::Zbase<Zchar, SP, AP>& rhs) { -#if defined FFS_WIN +#if defined FFS_WIN || defined FFS_MAC return z_impl::compareFilenamesNoCase(lhs.data(), rhs.data(), lhs.length(), rhs.length()); #elif defined FFS_LINUX return std::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filenames don't have embedded 0 -#elif defined FFS_MAC - return ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! + //#elif defined FFS_MAC + // return ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! #endif } |