diff options
Diffstat (limited to 'zen/FindFilePlus/find_file_plus.cpp')
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 141 |
1 files changed, 112 insertions, 29 deletions
diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index becfe553..e613177b 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -10,16 +10,18 @@ #include "load_dll.h" #include <zen/scope_guard.h> +#include <cstdio> + using namespace dll; using namespace findplus; namespace { -struct FileError +struct NtFileError //exception class { - FileError(ULONG errorCode) : win32Error(errorCode) {} - ULONG win32Error; + NtFileError(NTSTATUS errorCode) : ntError(errorCode) {} + NTSTATUS ntError; }; @@ -91,8 +93,8 @@ bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process //http://msdn.microsoft.com/en-us/library/ff563638(v=VS.85).aspx //verify dynamic dll binding - return ntOpenFile && - ntClose && + return ntOpenFile && + ntClose && ntQueryDirectoryFile && rtlNtStatusToDosError && rtlFreeUnicodeString && @@ -108,9 +110,15 @@ public: FileSearcher(const wchar_t* dirname); //throw FileError ~FileSearcher(); - void readdir(FileInformation& output); //throw FileError + void readDir(FileInformation& output) { (this->*readDirFun)(output); } //throw FileError + + bool tryFallbackToDefaultQuery(); //call if readDir() throws STATUS_NOT_SUPPORTED or similar private: + template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw FileError + //handle fallback, if retrieving file id is not supported by file system layer + void (FileSearcher::*readDirFun)(FileInformation& output); + UNICODE_STRING ntPathName; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; @@ -124,9 +132,43 @@ private: }; +namespace +{ +/* +Common C-style policy handling directory traversal: + +struct QueryPolicy +{ + typedef ... RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = ...; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo); +}; +*/ + +struct DirQueryDefault +{ + typedef FILE_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) { fileInfo.fileId.QuadPart = 0; } +}; + +struct DirQueryFileId +{ + typedef FILE_ID_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileIdBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) + { + fileInfo.fileId.QuadPart = rawInfo.FileId.QuadPart; //may be 0 even in this context, e.g. for mapped FTP drive! + static_assert(sizeof(fileInfo.fileId) == sizeof(rawInfo.FileId), "dang!"); + } +}; +} + + FileSearcher::FileSearcher(const wchar_t* dirname) : hDir(NULL), - nextEntryOffset(0) + nextEntryOffset(0), + readDirFun(&FileSearcher::readDirImpl<DirQueryFileId>) //start optimistically { ntPathName.Buffer = NULL; ntPathName.Length = 0; @@ -136,7 +178,8 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : //-------------------------------------------------------------------------------------------------------------- //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users - //in contrast to ::FindFirstFile() we don't evaluate the relativeName, however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative + //in contrast to ::FindFirstFile() implementation we don't evaluate the relativeName, + //however tests indicate ntFileName is *always* filled with an absolute name, even if dosFileName is relative //NOTE: RtlDosPathNameToNtPathName_U may be used on all XP/Win7/Win8 for compatibility // RtlDosPathNameToNtPathName_U: used by Windows XP available with OS version 3.51 (Windows NT) and higher @@ -145,7 +188,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : &ntPathName, //__out ntFileName, NULL, //__out_optFilePart, NULL)) //__out_opt relativeName - empty if dosFileName is absolute - throw FileError(rtlNtStatusToDosError(STATUS_OBJECT_PATH_NOT_FOUND)); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() + throw NtFileError(STATUS_OBJECT_PATH_NOT_FOUND); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() OBJECT_ATTRIBUTES objAttr = {}; InitializeObjectAttributes(&objAttr, //[out] POBJECT_ATTRIBUTES initializedAttributes, @@ -163,7 +206,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //__in ULONG shareAccess, - 7 on Win7/Win8, 3 on XP FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT); //__in ULONG openOptions - 4021 used on all XP/Win7/Win8 if (!NT_SUCCESS(rv)) - throw FileError(rtlNtStatusToDosError(rv)); + throw NtFileError(rv); } guardConstructor.dismiss(); @@ -179,16 +222,33 @@ FileSearcher::~FileSearcher() if (ntPathName.Buffer) rtlFreeUnicodeString(&ntPathName); //cleanup identical to ::FindFirstFile() - //note that most if this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" + //note that most of this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" +} + + +inline +bool FileSearcher::tryFallbackToDefaultQuery() +{ + if (readDirFun == &FileSearcher::readDirImpl<DirQueryDefault>) + return false; //already default + + //Note: NtQueryDirectoryFile() may not respect "restartScan" for some weird Win2000 file system drivers, so we won't bother + //Samba before v3.0.22 (Mar 30, 2006) seems to have a bug which sucessfully returns 128 elements via NtQueryDirectoryFile() + //and FileIdBothDirectoryInformation, then fails with STATUS_INVALID_LEVEL. + //Although traversal is NOT finished yet, it will further return STATUS_NO_MORE_FILES, even if falling back to FileBothDirectoryInformation!!! + + readDirFun = &FileSearcher::readDirImpl<DirQueryDefault>; + return true; } -void FileSearcher::readdir(FileInformation& output) +template <class QueryPolicy> +void FileSearcher::readDirImpl(FileInformation& output) //throw FileError { //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason //FILE_ID_BOTH_DIR_INFORMATION on the other hand works on XP/Win7/Win8 - //performance: there is no noticable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION + //performance: there is no noticeable difference between FILE_ID_BOTH_DIR_INFORMATION and FILE_ID_FULL_DIR_INFORMATION /* corresponding first access in ::FindFirstFileW() @@ -216,18 +276,26 @@ void FileSearcher::readdir(FileInformation& output) &status, //__out PIO_STATUS_BLOCK ioStatusBlock, &buffer, //__out_bcount(Length) PVOID fileInformation, BUFFER_SIZE, //__in ULONG length, ::FindNextFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * 2000 == 0x1000 - FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" + QueryPolicy::fileInformationClass, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" false, //__in BOOLEAN returnSingleEntry, NULL, //__in_opt PUNICODE_STRING mask, false); //__in BOOLEAN restartScan if (!NT_SUCCESS(rv)) - throw FileError(rtlNtStatusToDosError(rv)); //throws STATUS_NO_MORE_FILES when finished + { + if (rv == STATUS_NO_SUCH_FILE) //harmonize ntQueryDirectoryFile() error handling! failure to find a file on first call returns STATUS_NO_SUCH_FILE, + rv = STATUS_NO_MORE_FILES; //while on subsequent accesses return STATUS_NO_MORE_FILES + //note: not all directories contain "., .." entries! E.g. a drive's root directory or NetDrive + ftp.gnu.org\CRYPTO.README" + //-> addon: this is NOT a directory, it looks like on in NetDrive, but it's a file in Opera + + throw NtFileError(rv); //throws STATUS_NO_MORE_FILES when finished + } - if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): - throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! + if (status.Information == 0) //except for the first call to call ::NtQueryDirectoryFile(): + throw NtFileError(STATUS_BUFFER_OVERFLOW); //if buffer size is too small, return value is STATUS_SUCCESS and Information == 0 -> we don't expect this! } - const FILE_ID_BOTH_DIR_INFORMATION& dirInfo = *reinterpret_cast<FILE_ID_BOTH_DIR_INFORMATION*>(reinterpret_cast<char*>(buffer) + nextEntryOffset); + typedef typename QueryPolicy::RawFileInfo RawFileInfo; + const RawFileInfo& dirInfo = *reinterpret_cast<RawFileInfo*>(reinterpret_cast<char*>(buffer) + nextEntryOffset); if (dirInfo.NextEntryOffset == 0) nextEntryOffset = 0; //our offset is relative to the beginning of the buffer @@ -241,15 +309,16 @@ void FileSearcher::readdir(FileInformation& output) return tmp; }; + QueryPolicy::extractFileId(dirInfo, output); + output.creationTime = toFileTime(dirInfo.CreationTime); output.lastWriteTime = toFileTime(dirInfo.LastWriteTime); output.fileSize.QuadPart = dirInfo.EndOfFile.QuadPart; - output.fileId.QuadPart = dirInfo.FileId.QuadPart; output.fileAttributes = dirInfo.FileAttributes; output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a - throw FileError(rtlNtStatusToDosError(STATUS_BUFFER_OVERFLOW)); //short name of length MAX_PATH + 1, 0-termination is not required! + throw NtFileError(STATUS_BUFFER_OVERFLOW); //short name of length MAX_PATH + 1, 0-termination is not required! ::memcpy(output.shortName, dirInfo.FileName, dirInfo.FileNameLength); output.shortName[output.shortNameLength] = 0; //NOTE: FILE_ID_BOTH_DIR_INFORMATION::FileName in general is NOT 0-terminated! It is on XP/Win7, but NOT on Win8! @@ -257,7 +326,6 @@ void FileSearcher::readdir(FileInformation& output) static_assert(sizeof(output.creationTime) == sizeof(dirInfo.CreationTime), "dang!"); static_assert(sizeof(output.lastWriteTime) == sizeof(dirInfo.LastWriteTime), "dang!"); static_assert(sizeof(output.fileSize) == sizeof(dirInfo.EndOfFile), "dang!"); - static_assert(sizeof(output.fileId) == sizeof(dirInfo.FileId), "dang!"); static_assert(sizeof(output.fileAttributes) == sizeof(dirInfo.FileAttributes), "dang!"); } @@ -268,10 +336,10 @@ FindHandle findplus::openDir(const wchar_t* dirname) { return new FileSearcher(dirname); //throw FileError } - catch (const FileError& err) + catch (const NtFileError& e) { - setWin32Error(err.win32Error); - return NULL; + setWin32Error(rtlNtStatusToDosError(e.ntError)); + return nullptr; } } @@ -280,12 +348,28 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) { try { - hnd->readdir(output); //throw FileError + hnd->readDir(output); //throw FileError return true; } - catch (const FileError& err) + catch (const NtFileError& e) { - setWin32Error(err.win32Error); + /* + fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented + this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. + */ + if (e.ntError != STATUS_NO_MORE_FILES) + if (e.ntError == STATUS_INVALID_LEVEL || + e.ntError == STATUS_NOT_SUPPORTED || + e.ntError == STATUS_INVALID_PARAMETER || + e.ntError == STATUS_INVALID_NETWORK_RESPONSE || + e.ntError == STATUS_INVALID_INFO_CLASS || + e.ntError == STATUS_ACCESS_VIOLATION) //FileIdBothDirectoryInformation on XP accessing UDF + { + if (hnd->tryFallbackToDefaultQuery()) + return readDir(hnd, output); //implementation of tryFallbackToDefaultQuery() promises, that we don't land in an endless recursion here! + } + + setWin32Error(rtlNtStatusToDosError(e.ntError)); return false; } } @@ -293,6 +377,5 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) void findplus::closeDir(FindHandle hnd) { - if (hnd) //play a little "nice" - delete hnd; + delete hnd; } |