Skip to content

Commit

Permalink
Merge pull request #1937 from benpeart/fscache-NtQueryDirectoryFile-gfw
Browse files Browse the repository at this point in the history
 fscache: teach fscache to use NtQueryDirectoryFile
  • Loading branch information
dscho committed Apr 19, 2024
2 parents 8477e32 + 31126e6 commit fa4dd26
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 30 deletions.
123 changes: 93 additions & 30 deletions compat/win32/fscache.c
Expand Up @@ -7,6 +7,7 @@
#include "../../trace.h"
#include "config.h"
#include "../../mem-pool.h"
#include "ntifs.h"

static volatile long initialized;
static DWORD dwTlsIndex;
Expand All @@ -26,6 +27,13 @@ struct fscache {
unsigned int opendir_requests;
unsigned int fscache_requests;
unsigned int fscache_misses;
/*
* 32k wide characters translates to 64kB, which is the maximum that
* Windows 8.1 and earlier can handle. On network drives, not only
* the client's Windows version matters, but also the server's,
* therefore we need to keep this to 64kB.
*/
WCHAR buffer[32 * 1024];
};
static struct trace_key trace_fscache = TRACE_KEY_INIT(FSCACHE);

Expand Down Expand Up @@ -161,27 +169,44 @@ static void fsentry_release(struct fsentry *fse)
InterlockedDecrement(&(fse->u.refcnt));
}

static int xwcstoutfn(char *utf, int utflen, const wchar_t *wcs, int wcslen)
{
if (!wcs || !utf || utflen < 1) {
errno = EINVAL;
return -1;
}
utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, wcslen, utf, utflen, NULL, NULL);
if (utflen)
return utflen;
errno = ERANGE;
return -1;
}

/*
* Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
* Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure.
*/
static struct fsentry *fseentry_create_entry(struct fscache *cache,
struct fsentry *list,
const WIN32_FIND_DATAW *fdata)
PFILE_FULL_DIR_INFORMATION fdata)
{
char buf[MAX_PATH * 3];
int len;
struct fsentry *fse;
len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));

len = xwcstoutfn(buf, ARRAY_SIZE(buf), fdata->FileName, fdata->FileNameLength / sizeof(wchar_t));

fse = fsentry_alloc(cache, list, buf, len);

fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
fse->u.s.st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
| fdata->nFileSizeLow;
filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->u.s.st_atim));
filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->u.s.st_mtim));
filetime_to_timespec(&(fdata->ftCreationTime), &(fse->u.s.st_ctim));
fse->u.s.st_size = fdata->EndOfFile.LowPart |
(((off_t)fdata->EndOfFile.HighPart) << 32);
filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime),
&(fse->u.s.st_atim));
filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime),
&(fse->u.s.st_mtim));
filetime_to_timespec((FILETIME *)&(fdata->CreationTime),
&(fse->u.s.st_ctim));

return fse;
}
Expand All @@ -194,8 +219,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
int *dir_not_found)
{
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
WIN32_FIND_DATAW fdata;
wchar_t pattern[MAX_PATH];
NTSTATUS status;
IO_STATUS_BLOCK iosb;
PFILE_FULL_DIR_INFORMATION di;
HANDLE h;
int wlen;
struct fsentry *list, **phead;
Expand All @@ -211,15 +238,18 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
return NULL;
}

/* append optional '/' and wildcard '*' */
if (wlen)
pattern[wlen++] = '/';
pattern[wlen++] = '*';
pattern[wlen] = 0;
/* handle CWD */
if (!wlen) {
wlen = GetCurrentDirectoryW(ARRAY_SIZE(pattern), pattern);
if (!wlen || wlen >= ARRAY_SIZE(pattern)) {
errno = wlen ? ENAMETOOLONG : err_win_to_posix(GetLastError());
return NULL;
}
}

/* open find handle */
h = FindFirstFileExW(pattern, FindExInfoBasic, &fdata, FindExSearchNameMatch,
NULL, FIND_FIRST_EX_LARGE_FETCH);
h = CreateFileW(pattern, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (h == INVALID_HANDLE_VALUE) {
err = GetLastError();
*dir_not_found = 1; /* or empty directory */
Expand All @@ -236,22 +266,55 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f

/* walk directory and build linked list of fsentry structures */
phead = &list->next;
do {
*phead = fseentry_create_entry(cache, list, &fdata);
status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
if (!NT_SUCCESS(status)) {
/*
* NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when
* asked to enumerate an invalid directory (ie it is a file
* instead of a directory). Verify that is the actual cause
* of the error.
*/
if (status == STATUS_INVALID_PARAMETER) {
DWORD attributes = GetFileAttributesW(pattern);
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
status = ERROR_DIRECTORY;
}
goto Error;
}
di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
for (;;) {

*phead = fseentry_create_entry(cache, list, di);
phead = &(*phead)->next;
} while (FindNextFileW(h, &fdata));

/* remember result of last FindNextFile, then close find handle */
err = GetLastError();
FindClose(h);
/* If there is no offset in the entry, the buffer has been exhausted. */
if (di->NextEntryOffset == 0) {
status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
if (!NT_SUCCESS(status)) {
if (status == STATUS_NO_MORE_FILES)
break;
goto Error;
}

di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
continue;
}

/* Advance to the next entry. */
di = (PFILE_FULL_DIR_INFORMATION)(((PUCHAR)di) + di->NextEntryOffset);
}

/* return the list if we've got all the files */
if (err == ERROR_NO_MORE_FILES)
return list;
CloseHandle(h);
return list;

/* otherwise release the list and return error */
Error:
trace_printf_key(&trace_fscache,
"fscache: status(%ld) unable to query directory "
"contents '%s'\n", status, dir->dirent.d_name);
CloseHandle(h);
fsentry_release(list);
errno = err_win_to_posix(err);
return NULL;
}

Expand Down
131 changes: 131 additions & 0 deletions compat/win32/ntifs.h
@@ -0,0 +1,131 @@
#ifndef _NTIFS_
#define _NTIFS_

/*
* Copy necessary structures and definitions out of the Windows DDK
* to enable calling NtQueryDirectoryFile()
*/

typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

#if !defined(_NTSECAPI_) && !defined(_WINTERNL_) && \
!defined(__UNICODE_STRING_DEFINED)
#define __UNICODE_STRING_DEFINED
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;
#endif /* !_NTSECAPI_ && !_WINTERNL_ && !__UNICODE_STRING_DEFINED */

typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation,
FileBothDirectoryInformation,
FileBasicInformation,
FileStandardInformation,
FileInternalInformation,
FileEaInformation,
FileAccessInformation,
FileNameInformation,
FileRenameInformation,
FileLinkInformation,
FileNamesInformation,
FileDispositionInformation,
FilePositionInformation,
FileFullEaInformation,
FileModeInformation,
FileAlignmentInformation,
FileAllInformation,
FileAllocationInformation,
FileEndOfFileInformation,
FileAlternateNameInformation,
FileStreamInformation,
FilePipeInformation,
FilePipeLocalInformation,
FilePipeRemoteInformation,
FileMailslotQueryInformation,
FileMailslotSetInformation,
FileCompressionInformation,
FileObjectIdInformation,
FileCompletionInformation,
FileMoveClusterInformation,
FileQuotaInformation,
FileReparsePointInformation,
FileNetworkOpenInformation,
FileAttributeTagInformation,
FileTrackingInformation,
FileIdBothDirectoryInformation,
FileIdFullDirectoryInformation,
FileValidDataLengthInformation,
FileShortNameInformation,
FileIoCompletionNotificationInformation,
FileIoStatusBlockRangeInformation,
FileIoPriorityHintInformation,
FileSfioReserveInformation,
FileSfioVolumeInformation,
FileHardLinkInformation,
FileProcessIdsUsingFileInformation,
FileNormalizedNameInformation,
FileNetworkPhysicalNameInformation,
FileIdGlobalTxDirectoryInformation,
FileIsRemoteDeviceInformation,
FileAttributeCacheInformation,
FileNumaNodeInformation,
FileStandardLinkInformation,
FileRemoteProtocolInformation,
FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

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 _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
} u;
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef VOID
(NTAPI *PIO_APC_ROUTINE)(
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved);

NTSYSCALLAPI
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
);

#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)

#endif

0 comments on commit fa4dd26

Please sign in to comment.