4
4
#include "fscache.h"
5
5
#include "config.h"
6
6
#include "../../mem-pool.h"
7
+ #include "ntifs.h"
7
8
8
9
static volatile long initialized ;
9
10
static DWORD dwTlsIndex ;
@@ -23,6 +24,7 @@ struct fscache {
23
24
unsigned int opendir_requests ;
24
25
unsigned int fscache_requests ;
25
26
unsigned int fscache_misses ;
27
+ WCHAR buffer [64 * 1024 ];
26
28
};
27
29
static struct trace_key trace_fscache = TRACE_KEY_INIT (FSCACHE );
28
30
@@ -145,20 +147,44 @@ static void fsentry_release(struct fsentry *fse)
145
147
InterlockedDecrement (& (fse -> refcnt ));
146
148
}
147
149
150
+ static int xwcstoutfn (char * utf , int utflen , const wchar_t * wcs , int wcslen )
151
+ {
152
+ if (!wcs || !utf || utflen < 1 ) {
153
+ errno = EINVAL ;
154
+ return -1 ;
155
+ }
156
+ utflen = WideCharToMultiByte (CP_UTF8 , 0 , wcs , wcslen , utf , utflen , NULL , NULL );
157
+ if (utflen )
158
+ return utflen ;
159
+ errno = ERANGE ;
160
+ return -1 ;
161
+ }
162
+
148
163
/*
149
- * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
164
+ * Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure.
150
165
*/
151
166
static struct fsentry * fseentry_create_entry (struct fscache * cache , struct fsentry * list ,
152
- const WIN32_FIND_DATAW * fdata )
167
+ PFILE_FULL_DIR_INFORMATION fdata )
153
168
{
154
169
char buf [MAX_PATH * 3 ];
155
170
int len ;
156
171
struct fsentry * fse ;
157
- len = xwcstoutf (buf , fdata -> cFileName , ARRAY_SIZE (buf ));
172
+
173
+ len = xwcstoutfn (buf , ARRAY_SIZE (buf ), fdata -> FileName , fdata -> FileNameLength / sizeof (wchar_t ));
158
174
159
175
fse = fsentry_alloc (cache , list , buf , len );
160
176
161
- if (fdata -> dwReserved0 == IO_REPARSE_TAG_SYMLINK &&
177
+ /*
178
+ * On certain Windows versions, host directories mapped into
179
+ * Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/)
180
+ * look like symbolic links, but their targets are paths that
181
+ * are valid only in kernel mode.
182
+ *
183
+ * Let's work around this by detecting that situation and
184
+ * telling Git that these are *not* symbolic links.
185
+ */
186
+ if (fdata -> FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
187
+ fdata -> EaSize == IO_REPARSE_TAG_SYMLINK &&
162
188
sizeof (buf ) > (list ? list -> len + 1 : 0 ) + fse -> len + 1 &&
163
189
is_inside_windows_container ()) {
164
190
size_t off = 0 ;
@@ -171,13 +197,13 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
171
197
buf [off + fse -> len ] = '\0' ;
172
198
}
173
199
174
- fse -> st_mode = file_attr_to_st_mode (fdata -> dwFileAttributes ,
175
- fdata -> dwReserved0 , buf );
200
+ fse -> st_mode = file_attr_to_st_mode (fdata -> FileAttributes ,
201
+ fdata -> EaSize , buf );
176
202
fse -> st_size = S_ISLNK (fse -> st_mode ) ? MAX_LONG_PATH :
177
- fdata -> nFileSizeLow | (((off_t ) fdata -> nFileSizeHigh ) << 32 );
178
- filetime_to_timespec (& (fdata -> ftLastAccessTime ), & (fse -> st_atim ));
179
- filetime_to_timespec (& (fdata -> ftLastWriteTime ), & (fse -> st_mtim ));
180
- filetime_to_timespec (& (fdata -> ftCreationTime ), & (fse -> st_ctim ));
203
+ fdata -> EndOfFile . LowPart | (((off_t )fdata -> EndOfFile . HighPart ) << 32 );
204
+ filetime_to_timespec (( FILETIME * ) & (fdata -> LastAccessTime ), & (fse -> st_atim ));
205
+ filetime_to_timespec (( FILETIME * ) & (fdata -> LastWriteTime ), & (fse -> st_mtim ));
206
+ filetime_to_timespec (( FILETIME * ) & (fdata -> CreationTime ), & (fse -> st_ctim ));
181
207
182
208
return fse ;
183
209
}
@@ -190,8 +216,10 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsent
190
216
static struct fsentry * fsentry_create_list (struct fscache * cache , const struct fsentry * dir ,
191
217
int * dir_not_found )
192
218
{
193
- wchar_t pattern [MAX_LONG_PATH + 2 ]; /* + 2 for "\*" */
194
- WIN32_FIND_DATAW fdata ;
219
+ wchar_t pattern [MAX_LONG_PATH ];
220
+ NTSTATUS status ;
221
+ IO_STATUS_BLOCK iosb ;
222
+ PFILE_FULL_DIR_INFORMATION di ;
195
223
HANDLE h ;
196
224
int wlen ;
197
225
struct fsentry * list , * * phead ;
@@ -204,18 +232,18 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
204
232
dir -> len , MAX_PATH - 2 , core_long_paths )) < 0 )
205
233
return NULL ;
206
234
207
- /*
208
- * append optional '\' and wildcard '*'. Note: we need to use '\' as
209
- * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
210
- */
211
- if ( wlen )
212
- pattern [ wlen ++ ] = '\\' ;
213
- pattern [ wlen ++ ] = '*' ;
214
- pattern [ wlen ] = 0 ;
215
-
216
- /* open find handle */
217
- h = FindFirstFileExW ( pattern , FindExInfoBasic , & fdata , FindExSearchNameMatch ,
218
- NULL , FIND_FIRST_EX_LARGE_FETCH );
235
+ /* handle CWD */
236
+ if (! wlen ) {
237
+ wlen = GetCurrentDirectoryW ( ARRAY_SIZE ( pattern ), pattern );
238
+ if (! wlen || wlen >= ARRAY_SIZE ( pattern )) {
239
+ errno = wlen ? ENAMETOOLONG : err_win_to_posix ( GetLastError ());
240
+ return NULL ;
241
+ }
242
+ }
243
+
244
+ h = CreateFileW ( pattern , FILE_LIST_DIRECTORY ,
245
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
246
+ NULL , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS , NULL );
219
247
if (h == INVALID_HANDLE_VALUE ) {
220
248
err = GetLastError ();
221
249
* dir_not_found = 1 ; /* or empty directory */
@@ -231,22 +259,55 @@ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct f
231
259
232
260
/* walk directory and build linked list of fsentry structures */
233
261
phead = & list -> next ;
234
- do {
235
- * phead = fseentry_create_entry (cache , list , & fdata );
262
+ status = NtQueryDirectoryFile (h , NULL , 0 , 0 , & iosb , cache -> buffer ,
263
+ sizeof (cache -> buffer ), FileFullDirectoryInformation , FALSE, NULL , FALSE);
264
+ if (!NT_SUCCESS (status )) {
265
+ /*
266
+ * NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when
267
+ * asked to enumerate an invalid directory (ie it is a file
268
+ * instead of a directory). Verify that is the actual cause
269
+ * of the error.
270
+ */
271
+ if (status == STATUS_INVALID_PARAMETER ) {
272
+ DWORD attributes = GetFileAttributesW (pattern );
273
+ if (!(attributes & FILE_ATTRIBUTE_DIRECTORY ))
274
+ status = ERROR_DIRECTORY ;
275
+ }
276
+ goto Error ;
277
+ }
278
+ di = (PFILE_FULL_DIR_INFORMATION )(cache -> buffer );
279
+ for (;;) {
280
+
281
+ * phead = fseentry_create_entry (cache , list , di );
236
282
phead = & (* phead )-> next ;
237
- } while (FindNextFileW (h , & fdata ));
238
283
239
- /* remember result of last FindNextFile, then close find handle */
240
- err = GetLastError ();
241
- FindClose (h );
284
+ /* If there is no offset in the entry, the buffer has been exhausted. */
285
+ if (di -> NextEntryOffset == 0 ) {
286
+ status = NtQueryDirectoryFile (h , NULL , 0 , 0 , & iosb , cache -> buffer ,
287
+ sizeof (cache -> buffer ), FileFullDirectoryInformation , FALSE, NULL , FALSE);
288
+ if (!NT_SUCCESS (status )) {
289
+ if (status == STATUS_NO_MORE_FILES )
290
+ break ;
291
+ goto Error ;
292
+ }
293
+
294
+ di = (PFILE_FULL_DIR_INFORMATION )(cache -> buffer );
295
+ continue ;
296
+ }
297
+
298
+ /* Advance to the next entry. */
299
+ di = (PFILE_FULL_DIR_INFORMATION )(((PUCHAR )di ) + di -> NextEntryOffset );
300
+ }
242
301
243
- /* return the list if we've got all the files */
244
- if (err == ERROR_NO_MORE_FILES )
245
- return list ;
302
+ CloseHandle (h );
303
+ return list ;
246
304
247
- /* otherwise release the list and return error */
305
+ Error :
306
+ errno = (status == ERROR_DIRECTORY ) ? ENOTDIR : err_win_to_posix (status );
307
+ trace_printf_key (& trace_fscache , "fscache: error(%d) unable to query directory contents '%.*s'\n" ,
308
+ errno , dir -> len , dir -> name );
309
+ CloseHandle (h );
248
310
fsentry_release (list );
249
- errno = err_win_to_posix (err );
250
311
return NULL ;
251
312
}
252
313
0 commit comments