Skip to content

Commit 11d9f75

Browse files
committed
Merge pull request #1937 from benpeart/fscache-NtQueryDirectoryFile-gfw
fscache: teach fscache to use NtQueryDirectoryFile
2 parents 2d3f710 + 110d3e5 commit 11d9f75

File tree

2 files changed

+223
-35
lines changed

2 files changed

+223
-35
lines changed

compat/win32/fscache.c

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "fscache.h"
55
#include "config.h"
66
#include "../../mem-pool.h"
7+
#include "ntifs.h"
78

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

@@ -145,16 +153,30 @@ static void fsentry_release(struct fsentry *fse)
145153
InterlockedDecrement(&(fse->u.refcnt));
146154
}
147155

156+
static int xwcstoutfn(char *utf, int utflen, const wchar_t *wcs, int wcslen)
157+
{
158+
if (!wcs || !utf || utflen < 1) {
159+
errno = EINVAL;
160+
return -1;
161+
}
162+
utflen = WideCharToMultiByte(CP_UTF8, 0, wcs, wcslen, utf, utflen, NULL, NULL);
163+
if (utflen)
164+
return utflen;
165+
errno = ERANGE;
166+
return -1;
167+
}
168+
148169
/*
149-
* Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
170+
* Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure.
150171
*/
151172
static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsentry *list,
152-
const WIN32_FIND_DATAW *fdata)
173+
PFILE_FULL_DIR_INFORMATION fdata)
153174
{
154175
char buf[MAX_PATH * 3];
155176
int len;
156177
struct fsentry *fse;
157-
len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));
178+
179+
len = xwcstoutfn(buf, ARRAY_SIZE(buf), fdata->FileName, fdata->FileNameLength / sizeof(wchar_t));
158180

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

@@ -167,7 +189,8 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
167189
* Let's work around this by detecting that situation and
168190
* telling Git that these are *not* symbolic links.
169191
*/
170-
if (fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK &&
192+
if (fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
193+
fdata->EaSize == IO_REPARSE_TAG_SYMLINK &&
171194
sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 &&
172195
is_inside_windows_container()) {
173196
size_t off = 0;
@@ -180,13 +203,13 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
180203
buf[off + fse->len] = '\0';
181204
}
182205

183-
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes,
184-
fdata->dwReserved0, buf);
206+
fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
207+
fdata->EaSize, buf);
185208
fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
186-
fdata->nFileSizeLow | (((off_t) fdata->nFileSizeHigh) << 32);
187-
filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->u.s.st_atim));
188-
filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->u.s.st_mtim));
189-
filetime_to_timespec(&(fdata->ftCreationTime), &(fse->u.s.st_ctim));
209+
fdata->EndOfFile.LowPart | (((off_t)fdata->EndOfFile.HighPart) << 32);
210+
filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime), &(fse->u.s.st_atim));
211+
filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime), &(fse->u.s.st_mtim));
212+
filetime_to_timespec((FILETIME *)&(fdata->CreationTime), &(fse->u.s.st_ctim));
190213

191214
return fse;
192215
}
@@ -199,8 +222,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
199222
static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir,
200223
int *dir_not_found)
201224
{
202-
wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */
203-
WIN32_FIND_DATAW fdata;
225+
wchar_t pattern[MAX_LONG_PATH];
226+
NTSTATUS status;
227+
IO_STATUS_BLOCK iosb;
228+
PFILE_FULL_DIR_INFORMATION di;
204229
HANDLE h;
205230
int wlen;
206231
struct fsentry *list, **phead;
@@ -213,18 +238,18 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
213238
dir->len, MAX_PATH - 2, core_long_paths)) < 0)
214239
return NULL;
215240

216-
/*
217-
* append optional '\' and wildcard '*'. Note: we need to use '\' as
218-
* Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
219-
*/
220-
if (wlen)
221-
pattern[wlen++] = '\\';
222-
pattern[wlen++] = '*';
223-
pattern[wlen] = 0;
224-
225-
/* open find handle */
226-
h = FindFirstFileExW(pattern, FindExInfoBasic, &fdata, FindExSearchNameMatch,
227-
NULL, FIND_FIRST_EX_LARGE_FETCH);
241+
/* handle CWD */
242+
if (!wlen) {
243+
wlen = GetCurrentDirectoryW(ARRAY_SIZE(pattern), pattern);
244+
if (!wlen || wlen >= ARRAY_SIZE(pattern)) {
245+
errno = wlen ? ENAMETOOLONG : err_win_to_posix(GetLastError());
246+
return NULL;
247+
}
248+
}
249+
250+
h = CreateFileW(pattern, FILE_LIST_DIRECTORY,
251+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
252+
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
228253
if (h == INVALID_HANDLE_VALUE) {
229254
err = GetLastError();
230255
*dir_not_found = 1; /* or empty directory */
@@ -240,22 +265,54 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
240265

241266
/* walk directory and build linked list of fsentry structures */
242267
phead = &list->next;
243-
do {
244-
*phead = fseentry_create_entry(cache, list, &fdata);
268+
status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
269+
sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
270+
if (!NT_SUCCESS(status)) {
271+
/*
272+
* NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when
273+
* asked to enumerate an invalid directory (ie it is a file
274+
* instead of a directory). Verify that is the actual cause
275+
* of the error.
276+
*/
277+
if (status == STATUS_INVALID_PARAMETER) {
278+
DWORD attributes = GetFileAttributesW(pattern);
279+
if (!(attributes & FILE_ATTRIBUTE_DIRECTORY))
280+
status = ERROR_DIRECTORY;
281+
}
282+
goto Error;
283+
}
284+
di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
285+
for (;;) {
286+
287+
*phead = fseentry_create_entry(cache, list, di);
245288
phead = &(*phead)->next;
246-
} while (FindNextFileW(h, &fdata));
247289

248-
/* remember result of last FindNextFile, then close find handle */
249-
err = GetLastError();
250-
FindClose(h);
290+
/* If there is no offset in the entry, the buffer has been exhausted. */
291+
if (di->NextEntryOffset == 0) {
292+
status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer,
293+
sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE);
294+
if (!NT_SUCCESS(status)) {
295+
if (status == STATUS_NO_MORE_FILES)
296+
break;
297+
goto Error;
298+
}
299+
300+
di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer);
301+
continue;
302+
}
303+
304+
/* Advance to the next entry. */
305+
di = (PFILE_FULL_DIR_INFORMATION)(((PUCHAR)di) + di->NextEntryOffset);
306+
}
251307

252-
/* return the list if we've got all the files */
253-
if (err == ERROR_NO_MORE_FILES)
254-
return list;
308+
CloseHandle(h);
309+
return list;
255310

256-
/* otherwise release the list and return error */
311+
Error:
312+
trace_printf_key(&trace_fscache, "fscache: status(%ld) unable to query directory contents '%.*s'\n",
313+
status, dir->len, dir->name);
314+
CloseHandle(h);
257315
fsentry_release(list);
258-
errno = err_win_to_posix(err);
259316
return NULL;
260317
}
261318

compat/win32/ntifs.h

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#ifndef _NTIFS_
2+
#define _NTIFS_
3+
4+
/*
5+
* Copy necessary structures and definitions out of the Windows DDK
6+
* to enable calling NtQueryDirectoryFile()
7+
*/
8+
9+
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
10+
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
11+
12+
typedef struct _UNICODE_STRING {
13+
USHORT Length;
14+
USHORT MaximumLength;
15+
#ifdef MIDL_PASS
16+
[size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT * Buffer;
17+
#else // MIDL_PASS
18+
_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer;
19+
#endif // MIDL_PASS
20+
} UNICODE_STRING;
21+
typedef UNICODE_STRING *PUNICODE_STRING;
22+
typedef const UNICODE_STRING *PCUNICODE_STRING;
23+
24+
typedef enum _FILE_INFORMATION_CLASS {
25+
FileDirectoryInformation = 1,
26+
FileFullDirectoryInformation,
27+
FileBothDirectoryInformation,
28+
FileBasicInformation,
29+
FileStandardInformation,
30+
FileInternalInformation,
31+
FileEaInformation,
32+
FileAccessInformation,
33+
FileNameInformation,
34+
FileRenameInformation,
35+
FileLinkInformation,
36+
FileNamesInformation,
37+
FileDispositionInformation,
38+
FilePositionInformation,
39+
FileFullEaInformation,
40+
FileModeInformation,
41+
FileAlignmentInformation,
42+
FileAllInformation,
43+
FileAllocationInformation,
44+
FileEndOfFileInformation,
45+
FileAlternateNameInformation,
46+
FileStreamInformation,
47+
FilePipeInformation,
48+
FilePipeLocalInformation,
49+
FilePipeRemoteInformation,
50+
FileMailslotQueryInformation,
51+
FileMailslotSetInformation,
52+
FileCompressionInformation,
53+
FileObjectIdInformation,
54+
FileCompletionInformation,
55+
FileMoveClusterInformation,
56+
FileQuotaInformation,
57+
FileReparsePointInformation,
58+
FileNetworkOpenInformation,
59+
FileAttributeTagInformation,
60+
FileTrackingInformation,
61+
FileIdBothDirectoryInformation,
62+
FileIdFullDirectoryInformation,
63+
FileValidDataLengthInformation,
64+
FileShortNameInformation,
65+
FileIoCompletionNotificationInformation,
66+
FileIoStatusBlockRangeInformation,
67+
FileIoPriorityHintInformation,
68+
FileSfioReserveInformation,
69+
FileSfioVolumeInformation,
70+
FileHardLinkInformation,
71+
FileProcessIdsUsingFileInformation,
72+
FileNormalizedNameInformation,
73+
FileNetworkPhysicalNameInformation,
74+
FileIdGlobalTxDirectoryInformation,
75+
FileIsRemoteDeviceInformation,
76+
FileAttributeCacheInformation,
77+
FileNumaNodeInformation,
78+
FileStandardLinkInformation,
79+
FileRemoteProtocolInformation,
80+
FileMaximumInformation
81+
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
82+
83+
typedef struct _FILE_FULL_DIR_INFORMATION {
84+
ULONG NextEntryOffset;
85+
ULONG FileIndex;
86+
LARGE_INTEGER CreationTime;
87+
LARGE_INTEGER LastAccessTime;
88+
LARGE_INTEGER LastWriteTime;
89+
LARGE_INTEGER ChangeTime;
90+
LARGE_INTEGER EndOfFile;
91+
LARGE_INTEGER AllocationSize;
92+
ULONG FileAttributes;
93+
ULONG FileNameLength;
94+
ULONG EaSize;
95+
WCHAR FileName[1];
96+
} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION;
97+
98+
typedef struct _IO_STATUS_BLOCK {
99+
union {
100+
NTSTATUS Status;
101+
PVOID Pointer;
102+
} u;
103+
ULONG_PTR Information;
104+
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
105+
106+
typedef VOID
107+
(NTAPI *PIO_APC_ROUTINE)(
108+
IN PVOID ApcContext,
109+
IN PIO_STATUS_BLOCK IoStatusBlock,
110+
IN ULONG Reserved);
111+
112+
NTSYSCALLAPI
113+
NTSTATUS
114+
NTAPI
115+
NtQueryDirectoryFile(
116+
_In_ HANDLE FileHandle,
117+
_In_opt_ HANDLE Event,
118+
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
119+
_In_opt_ PVOID ApcContext,
120+
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
121+
_Out_writes_bytes_(Length) PVOID FileInformation,
122+
_In_ ULONG Length,
123+
_In_ FILE_INFORMATION_CLASS FileInformationClass,
124+
_In_ BOOLEAN ReturnSingleEntry,
125+
_In_opt_ PUNICODE_STRING FileName,
126+
_In_ BOOLEAN RestartScan
127+
);
128+
129+
#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
130+
131+
#endif

0 commit comments

Comments
 (0)