mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
sys: FspFsvolQueryDirectoryCopy: add missing continue
A single line change in FspFsvolQueryDirectoryCopy fixes GitHub issue #475. This commit also includes a test for detecting duplicate directory entries. Credit for the investigation and reproduction of this issue goes to GitHub user @hach-que.
This commit is contained in:
parent
874a223bcc
commit
97c075e744
@ -205,6 +205,7 @@ static NTSTATUS FspFsvolQueryDirectoryCopy(
|
|||||||
ASSERT(sizeof(UINT64) == DirectoryMarker->Length);
|
ASSERT(sizeof(UINT64) == DirectoryMarker->Length);
|
||||||
DirectoryMarkerFound = DirectoryNextOffset == *(PUINT64)DirectoryMarker->Buffer;
|
DirectoryMarkerFound = DirectoryNextOffset == *(PUINT64)DirectoryMarker->Buffer;
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CopyLength is the same as FileName.Length except on STATUS_BUFFER_OVERFLOW */
|
/* CopyLength is the same as FileName.Length except on STATUS_BUFFER_OVERFLOW */
|
||||||
|
@ -38,6 +38,7 @@ enum
|
|||||||
MemfsCaseInsensitive = 0x80000000,
|
MemfsCaseInsensitive = 0x80000000,
|
||||||
MemfsFlushAndPurgeOnCleanup = 0x40000000,
|
MemfsFlushAndPurgeOnCleanup = 0x40000000,
|
||||||
MemfsLegacyUnlinkRename = 0x20000000,
|
MemfsLegacyUnlinkRename = 0x20000000,
|
||||||
|
MemfsNoSlowio = 0x10000000,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\
|
#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\
|
||||||
|
@ -28,6 +28,52 @@
|
|||||||
|
|
||||||
#include "winfsp-tests.h"
|
#include "winfsp-tests.h"
|
||||||
|
|
||||||
|
typedef struct _FILE_FULL_DIR_INFORMATION
|
||||||
|
{
|
||||||
|
ULONG NextEntryOffset;
|
||||||
|
ULONG FileIndex;
|
||||||
|
LARGE_INTEGER CreationTime;
|
||||||
|
LARGE_INTEGER LastAccessTime;
|
||||||
|
LARGE_INTEGER LastWriteTime;
|
||||||
|
LARGE_INTEGER ChangeTime;
|
||||||
|
LARGE_INTEGER EndOfFile;
|
||||||
|
LARGE_INTEGER AllocationSize;
|
||||||
|
ULONG FileAttributes;
|
||||||
|
ULONG FileNameLength;
|
||||||
|
ULONG EaSize;
|
||||||
|
WCHAR FileName[1];
|
||||||
|
} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION;
|
||||||
|
|
||||||
|
typedef struct _FILE_DIRECTORY_INFORMATION
|
||||||
|
{
|
||||||
|
ULONG NextEntryOffset;
|
||||||
|
ULONG FileIndex;
|
||||||
|
LARGE_INTEGER CreationTime;
|
||||||
|
LARGE_INTEGER LastAccessTime;
|
||||||
|
LARGE_INTEGER LastWriteTime;
|
||||||
|
LARGE_INTEGER ChangeTime;
|
||||||
|
LARGE_INTEGER EndOfFile;
|
||||||
|
LARGE_INTEGER AllocationSize;
|
||||||
|
ULONG FileAttributes;
|
||||||
|
ULONG FileNameLength;
|
||||||
|
WCHAR FileName[1];
|
||||||
|
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
|
||||||
|
|
||||||
|
NTSTATUS
|
||||||
|
NTAPI
|
||||||
|
NtQueryDirectoryFile (
|
||||||
|
HANDLE FileHandle,
|
||||||
|
HANDLE Event,
|
||||||
|
PIO_APC_ROUTINE ApcRoutine,
|
||||||
|
PVOID ApcContext,
|
||||||
|
PIO_STATUS_BLOCK IoStatusBlock,
|
||||||
|
PVOID FileInformation,
|
||||||
|
ULONG Length,
|
||||||
|
FILE_INFORMATION_CLASS FileInformationClass,
|
||||||
|
BOOLEAN ReturnSingleEntry,
|
||||||
|
PUNICODE_STRING FileName,
|
||||||
|
BOOLEAN RestartScan);
|
||||||
|
|
||||||
static void querydir_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
static void querydir_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
||||||
{
|
{
|
||||||
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
||||||
@ -269,6 +315,212 @@ void querydir_test(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline size_t hash_chars(const wchar_t *s, size_t length)
|
||||||
|
{
|
||||||
|
/* djb2: see http://www.cse.yorku.ca/~oz/hash.html */
|
||||||
|
size_t h = 5381;
|
||||||
|
for (const wchar_t *t = s + length; t > s; ++s)
|
||||||
|
h = 33 * h + *s;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
static NTSTATUS querydir_nodup_dotest_thread_loop(void *FilePath)
|
||||||
|
{
|
||||||
|
NTSTATUS Result;
|
||||||
|
void *Buffer = 0;
|
||||||
|
HANDLE Handle = INVALID_HANDLE_VALUE;
|
||||||
|
IO_STATUS_BLOCK IoStatus;
|
||||||
|
size_t hash[64], hash_count = 0;
|
||||||
|
|
||||||
|
Buffer = malloc(4096);
|
||||||
|
if (0 == Buffer)
|
||||||
|
{
|
||||||
|
Result = STATUS_INSUFFICIENT_RESOURCES;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle = CreateFileW(
|
||||||
|
FilePath,
|
||||||
|
FILE_LIST_DIRECTORY,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||||
|
0,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS,
|
||||||
|
0);
|
||||||
|
if (INVALID_HANDLE_VALUE == Handle)
|
||||||
|
{
|
||||||
|
Result = FspNtStatusFromWin32(GetLastError());
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
Result = NtQueryDirectoryFile(
|
||||||
|
Handle,
|
||||||
|
0, 0, 0,
|
||||||
|
&IoStatus,
|
||||||
|
Buffer,
|
||||||
|
4096,
|
||||||
|
2/*FileFullDirectoryInformation*/,
|
||||||
|
FALSE,
|
||||||
|
0,
|
||||||
|
FALSE);
|
||||||
|
if (STATUS_NO_MORE_FILES == Result)
|
||||||
|
break;
|
||||||
|
if (!NT_SUCCESS(Result))
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
for (FILE_FULL_DIR_INFORMATION *DirInfo = Buffer;;
|
||||||
|
DirInfo = (PVOID)((PUINT8)DirInfo + DirInfo->NextEntryOffset))
|
||||||
|
{
|
||||||
|
size_t h = hash_chars(DirInfo->FileName, DirInfo->FileNameLength / sizeof(WCHAR));
|
||||||
|
for (size_t i = 0; hash_count > i; i++)
|
||||||
|
if (hash[i] == h)
|
||||||
|
{
|
||||||
|
WCHAR FileName[256];
|
||||||
|
memcpy(FileName, DirInfo->FileName, DirInfo->FileNameLength);
|
||||||
|
FileName[DirInfo->FileNameLength / sizeof(WCHAR)] = L'\0';
|
||||||
|
FspDebugLog(__FUNCTION__ ": duplicate \"%S\"\n", FileName);
|
||||||
|
Result = STATUS_UNSUCCESSFUL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (sizeof hash / sizeof hash[0] > hash_count)
|
||||||
|
hash[hash_count++] = h;
|
||||||
|
if (0 == DirInfo->NextEntryOffset)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = STATUS_SUCCESS;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
if (INVALID_HANDLE_VALUE != Handle)
|
||||||
|
CloseHandle(Handle);
|
||||||
|
|
||||||
|
free(Buffer);
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
static unsigned __stdcall querydir_nodup_dotest_thread(void *FilePath)
|
||||||
|
{
|
||||||
|
FspDebugLog(__FUNCTION__ ": \"%S\"\n", FilePath);
|
||||||
|
|
||||||
|
NTSTATUS Result;
|
||||||
|
|
||||||
|
for (size_t i = 0; 5000 > i; i++)
|
||||||
|
{
|
||||||
|
Result = querydir_nodup_dotest_thread_loop(FilePath);
|
||||||
|
if (!NT_SUCCESS(Result))
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = STATUS_SUCCESS;
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void querydir_nodup_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
|
||||||
|
{
|
||||||
|
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
||||||
|
|
||||||
|
static WCHAR *FileNames[] =
|
||||||
|
{
|
||||||
|
L"Extensions.cs",
|
||||||
|
L"JsonSchema.cs",
|
||||||
|
L"JsonSchemaBuilder.cs",
|
||||||
|
L"JsonSchemaConstants.cs",
|
||||||
|
L"JsonSchemaException.cs",
|
||||||
|
L"JsonSchemaGenerator.cs",
|
||||||
|
L"JsonSchemaModel.cs",
|
||||||
|
L"JsonSchemaModelBuilder.cs",
|
||||||
|
L"JsonSchemaNode.cs",
|
||||||
|
L"JsonSchemaNodeCollection.cs",
|
||||||
|
L"JsonSchemaResolver.cs",
|
||||||
|
L"JsonSchemaType.cs",
|
||||||
|
L"JsonSchemaWriter.cs",
|
||||||
|
L"UndefinedSchemaIdHandling.cs",
|
||||||
|
L"ValidationEventArgs.cs",
|
||||||
|
L"ValidationEventHandler.cs",
|
||||||
|
};
|
||||||
|
HANDLE Handle;
|
||||||
|
BOOL Success;
|
||||||
|
WCHAR FilePath[MAX_PATH];
|
||||||
|
HANDLE Threads[2];
|
||||||
|
DWORD ExitCode;
|
||||||
|
|
||||||
|
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir0",
|
||||||
|
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
|
||||||
|
Success = CreateDirectoryW(FilePath, 0);
|
||||||
|
ASSERT(Success);
|
||||||
|
|
||||||
|
for (size_t i = 0; sizeof FileNames / sizeof FileNames[0] > i; i++)
|
||||||
|
{
|
||||||
|
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir0\\%s",
|
||||||
|
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs), FileNames[i]);
|
||||||
|
Handle = CreateFileW(FilePath, GENERIC_ALL, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
ASSERT(INVALID_HANDLE_VALUE != Handle);
|
||||||
|
Success = CloseHandle(Handle);
|
||||||
|
ASSERT(Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir0",
|
||||||
|
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
|
||||||
|
for (size_t i = 0; sizeof Threads / sizeof Threads[0] > i; i++)
|
||||||
|
{
|
||||||
|
Threads[i] = (HANDLE)_beginthreadex(0, 0, querydir_nodup_dotest_thread, FilePath, 0, 0);
|
||||||
|
ASSERT(0 != Threads[i]);
|
||||||
|
}
|
||||||
|
for (size_t i = 0; sizeof Threads / sizeof Threads[0] > i; i++)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(Threads[i], INFINITE);
|
||||||
|
GetExitCodeThread(Threads[i], &ExitCode);
|
||||||
|
CloseHandle(Threads[i]);
|
||||||
|
ASSERT(0 == ExitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; sizeof FileNames / sizeof FileNames[0] > i; i++)
|
||||||
|
{
|
||||||
|
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir0\\%s",
|
||||||
|
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs), FileNames[i]);
|
||||||
|
Success = DeleteFileW(FilePath);
|
||||||
|
ASSERT(Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir0",
|
||||||
|
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
|
||||||
|
Success = RemoveDirectoryW(FilePath);
|
||||||
|
ASSERT(Success);
|
||||||
|
|
||||||
|
memfs_stop(memfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void querydir_nodup_test(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Test GitHub issue #475. Credit for the investigation and reproduction
|
||||||
|
* of this issue goes to GitHub user @hach-que.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (NtfsTests)
|
||||||
|
{
|
||||||
|
WCHAR DirBuf[MAX_PATH];
|
||||||
|
GetTestDirectory(DirBuf);
|
||||||
|
querydir_nodup_dotest(-1, DirBuf, 0);
|
||||||
|
}
|
||||||
|
if (WinFspDiskTests)
|
||||||
|
{
|
||||||
|
querydir_nodup_dotest(MemfsDisk | MemfsNoSlowio, 0, 0);
|
||||||
|
querydir_nodup_dotest(MemfsDisk | MemfsNoSlowio, 0, 1000);
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
if (WinFspNetTests)
|
||||||
|
{
|
||||||
|
querydir_nodup_dotest(MemfsNet | MemfsNoSlowio, L"\\\\memfs\\share", 0);
|
||||||
|
querydir_nodup_dotest(MemfsNet | MemfsNoSlowio, L"\\\\memfs\\share", 1000);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static void querydir_single_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
static void querydir_single_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
||||||
{
|
{
|
||||||
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
||||||
@ -368,36 +620,6 @@ void querydir_expire_cache_test(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct _FILE_DIRECTORY_INFORMATION {
|
|
||||||
ULONG NextEntryOffset;
|
|
||||||
ULONG FileIndex;
|
|
||||||
LARGE_INTEGER CreationTime;
|
|
||||||
LARGE_INTEGER LastAccessTime;
|
|
||||||
LARGE_INTEGER LastWriteTime;
|
|
||||||
LARGE_INTEGER ChangeTime;
|
|
||||||
LARGE_INTEGER EndOfFile;
|
|
||||||
LARGE_INTEGER AllocationSize;
|
|
||||||
ULONG FileAttributes;
|
|
||||||
ULONG FileNameLength;
|
|
||||||
WCHAR FileName[1];
|
|
||||||
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;
|
|
||||||
|
|
||||||
NTSTATUS
|
|
||||||
NTAPI
|
|
||||||
NtQueryDirectoryFile (
|
|
||||||
_In_ HANDLE FileHandle,
|
|
||||||
_In_opt_ HANDLE Event,
|
|
||||||
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
|
|
||||||
_In_opt_ PVOID ApcContext,
|
|
||||||
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
|
|
||||||
_Out_writes_bytes_(Length) PVOID FileInformation,
|
|
||||||
_In_ ULONG Length,
|
|
||||||
_In_ FILE_INFORMATION_CLASS FileInformationClass,
|
|
||||||
_In_ BOOLEAN ReturnSingleEntry,
|
|
||||||
_In_opt_ PUNICODE_STRING FileName,
|
|
||||||
_In_ BOOLEAN RestartScan
|
|
||||||
);
|
|
||||||
|
|
||||||
static void querydir_buffer_overflow_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
static void querydir_buffer_overflow_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout, ULONG SleepTimeout)
|
||||||
{
|
{
|
||||||
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
|
||||||
@ -766,6 +988,7 @@ void dirnotify_test(void)
|
|||||||
void dirctl_tests(void)
|
void dirctl_tests(void)
|
||||||
{
|
{
|
||||||
TEST(querydir_test);
|
TEST(querydir_test);
|
||||||
|
TEST_OPT(querydir_nodup_test);
|
||||||
if (!OptShareName)
|
if (!OptShareName)
|
||||||
TEST_OPT(querydir_single_test);
|
TEST_OPT(querydir_single_test);
|
||||||
TEST(querydir_expire_cache_test);
|
TEST(querydir_expire_cache_test);
|
||||||
|
@ -48,9 +48,9 @@ void *memfs_start_ex(ULONG Flags, ULONG FileInfoTimeout)
|
|||||||
FileInfoTimeout,
|
FileInfoTimeout,
|
||||||
1024,
|
1024,
|
||||||
1024 * 1024,
|
1024 * 1024,
|
||||||
50, /*SlowioMaxDelay*/
|
(Flags & MemfsNoSlowio) ? 0 : 50, /*SlowioMaxDelay*/
|
||||||
10, /*SlowioPercentDelay*/
|
(Flags & MemfsNoSlowio) ? 0 : 10, /*SlowioPercentDelay*/
|
||||||
5, /*SlowioRarefyDelay*/
|
(Flags & MemfsNoSlowio) ? 0 : 5, /*SlowioRarefyDelay*/
|
||||||
0,
|
0,
|
||||||
MemfsNet == Flags ? L"\\memfs\\share" : 0,
|
MemfsNet == Flags ? L"\\memfs\\share" : 0,
|
||||||
0,
|
0,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user