diff options
Diffstat (limited to 'zen/file_traverser.cpp')
-rw-r--r-- | zen/file_traverser.cpp | 60 |
1 files changed, 32 insertions, 28 deletions
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 3ef94032..facce75c 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -39,15 +39,15 @@ void zen::traverseFolder(const Zstring& dirPath, HANDLE hDir = ::FindFirstFile(applyLongPathPrefix(appendSeparator(dirPath) + L'*').c_str(), &findData); if (hDir == INVALID_HANDLE_VALUE) { - const DWORD lastError = ::GetLastError(); //copy before making other system calls! - if (lastError == ERROR_FILE_NOT_FOUND) + const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! + if (ec == ERROR_FILE_NOT_FOUND) { //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility if (dirExists(dirPath)) //yes, a race-condition, still the best we can do return; } - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"FindFirstFile", lastError); + throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindFirstFile", ec)); } ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); @@ -58,37 +58,39 @@ void zen::traverseFolder(const Zstring& dirPath, firstIteration = false; else if (!::FindNextFile(hDir, &findData)) { - const DWORD lastError = ::GetLastError(); - if (lastError == ERROR_NO_MORE_FILES) //not an error situation + const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! + if (ec == ERROR_NO_MORE_FILES) //not an error situation return; //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile", lastError); + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindNextFile", ec)); } //skip "." and ".." - const Zchar* const shortName = findData.cFileName; + const wchar_t* const itemNameRaw = findData.cFileName; - if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item is missing a name."); - if (shortName[0] == L'.' && - (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) + if (itemNameRaw[0] == 0) + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); + + if (itemNameRaw[0] == L'.' && + (itemNameRaw[1] == 0 || (itemNameRaw[1] == L'.' && itemNameRaw[2] == 0))) continue; - const Zstring& itempath = appendSeparator(dirPath) + shortName; + const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; if (zen::isSymlink(findData)) //check first! { if (onLink) - onLink({ shortName, itempath, filetimeToTimeT(findData.ftLastWriteTime) }); + onLink({ itemPath, filetimeToTimeT(findData.ftLastWriteTime) }); } else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { if (onDir) - onDir({ shortName, itempath }); + onDir({ itemPath }); } else //a file { if (onFile) - onFile({ shortName, itempath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); + onFile({ itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); } } @@ -106,32 +108,34 @@ void zen::traverseFolder(const Zstring& dirPath, DIR* dirObj = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir", getLastError()); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir"); ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash for (;;) { struct ::dirent* dirEntry = nullptr; if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r", getLastError()); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r"); //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx if (!dirEntry) //no more items return; //don't return "." and ".." - const char* shortName = dirEntry->d_name; + const char* itemNameRaw = dirEntry->d_name; + + if (itemNameRaw[0] == 0) + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name."); - if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item is missing a name."); - if (shortName[0] == '.' && - (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) + if (itemNameRaw[0] == '.' && + (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0))) continue; #ifdef ZEN_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)) + if (CFStringRef cfStr = osx::createCFString(itemNameRaw)) { ZEN_ON_SCOPE_EXIT(::CFRelease(cfStr)); @@ -140,19 +144,19 @@ void zen::traverseFolder(const Zstring& dirPath, { bufferUtfDecomposed.resize(lenMax); if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation - shortName = &bufferUtfDecomposed[0]; + itemNameRaw = &bufferUtfDecomposed[0]; } } //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; //const char* samplePrecomposed = "\xc3\xb3.txt"; #endif - const Zstring& itempath = appendSeparator(dirPath) + shortName; + const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; struct ::stat statData = {}; try { - if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itempath)), L"lstat", getLastError()); + if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat"); } catch (const FileError& e) { @@ -164,17 +168,17 @@ void zen::traverseFolder(const Zstring& dirPath, if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { if (onLink) - onLink({ shortName, itempath, statData.st_mtime}); + onLink({ itemPath, statData.st_mtime}); } else if (S_ISDIR(statData.st_mode)) //a directory { if (onDir) - onDir({ shortName, itempath }); + onDir({ itemPath }); } else //a file or named pipe, ect. { if (onFile) - onFile({ shortName, itempath, makeUnsigned(statData.st_size), statData.st_mtime }); + onFile({ itemPath, makeUnsigned(statData.st_size), statData.st_mtime }); } /* It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: |