summaryrefslogtreecommitdiff
path: root/zen/FindFilePlus/find_file_plus.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/FindFilePlus/find_file_plus.cpp')
-rw-r--r--zen/FindFilePlus/find_file_plus.cpp141
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;
}
bgstack15