VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/nt/direnum-r3-nt.cpp

Last change on this file was 107382, checked in by vboxsync, 4 weeks ago

IPRT/r3/nt: Some FILE_INFORMATION_CLASS doc updates for NT4 and earlier. bugref:10826

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 39.2 KB
Line 
1/* $Id: direnum-r3-nt.cpp 107382 2024-12-19 13:55:07Z vboxsync $ */
2/** @file
3 * IPRT - Directory Enumeration, Native NT.
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#define LOG_GROUP RTLOGGROUP_DIR
42#include "internal-r3-nt.h"
43
44#include <iprt/dir.h>
45#include <iprt/path.h>
46#include <iprt/mem.h>
47#include <iprt/string.h>
48#include <iprt/assert.h>
49#include <iprt/err.h>
50#include <iprt/file.h>
51#include <iprt/log.h>
52#include <iprt/utf16.h>
53#include "internal/fs.h"
54#include "internal/dir.h"
55#include "internal/path.h"
56#include "../win/internal-r3-win.h"
57
58
59/*********************************************************************************************************************************
60* Defined Constants And Macros *
61*********************************************************************************************************************************/
62/** Whether to return a single record (TRUE) or multiple (FALSE). */
63#define RTDIR_NT_SINGLE_RECORD FALSE
64
65/** Go hard on record chaining (has slight performance impact). */
66#ifdef RT_STRICT
67# define RTDIR_NT_STRICT
68#endif
69
70
71/* ASSUMES FileID comes after ShortName and the structs are identical up to that point. */
72AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, NextEntryOffset, FILE_ID_BOTH_DIR_INFORMATION, NextEntryOffset);
73AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileIndex , FILE_ID_BOTH_DIR_INFORMATION, FileIndex );
74AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, CreationTime , FILE_ID_BOTH_DIR_INFORMATION, CreationTime );
75AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastAccessTime , FILE_ID_BOTH_DIR_INFORMATION, LastAccessTime );
76AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, LastWriteTime , FILE_ID_BOTH_DIR_INFORMATION, LastWriteTime );
77AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ChangeTime , FILE_ID_BOTH_DIR_INFORMATION, ChangeTime );
78AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EndOfFile , FILE_ID_BOTH_DIR_INFORMATION, EndOfFile );
79AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, AllocationSize , FILE_ID_BOTH_DIR_INFORMATION, AllocationSize );
80AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileAttributes , FILE_ID_BOTH_DIR_INFORMATION, FileAttributes );
81AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, FileNameLength , FILE_ID_BOTH_DIR_INFORMATION, FileNameLength );
82AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, EaSize , FILE_ID_BOTH_DIR_INFORMATION, EaSize );
83AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortNameLength, FILE_ID_BOTH_DIR_INFORMATION, ShortNameLength);
84AssertCompileMembersSameSizeAndOffset(FILE_BOTH_DIR_INFORMATION, ShortName , FILE_ID_BOTH_DIR_INFORMATION, ShortName );
85
86
87
88size_t rtDirNativeGetStructSize(const char *pszPath)
89{
90 NOREF(pszPath);
91 return sizeof(RTDIRINTERNAL);
92}
93
94
95int rtDirNativeOpen(PRTDIRINTERNAL pDir, uintptr_t hRelativeDir, void *pvNativeRelative)
96{
97 /*
98 * Convert the filter to UTF-16.
99 */
100 int rc;
101 pDir->pNtFilterStr = NULL;
102 if ( pDir->cchFilter > 0
103 && pDir->enmFilter == RTDIRFILTER_WINNT)
104 {
105 PRTUTF16 pwszTmp;
106 rc = RTStrToUtf16(pDir->pszFilter, &pwszTmp);
107 if (RT_FAILURE(rc))
108 return rc;
109 pDir->NtFilterStr.Buffer = pwszTmp;
110 pDir->NtFilterStr.Length = pDir->NtFilterStr.MaximumLength = (uint16_t)(RTUtf16Len(pwszTmp) * sizeof(RTUTF16));
111 pDir->pNtFilterStr = &pDir->NtFilterStr;
112 }
113
114 /*
115 * Try open the directory
116 */
117#ifdef IPRT_WITH_NT_PATH_PASSTHRU
118 bool fObjDir = false;
119#endif
120 if (hRelativeDir != ~(uintptr_t)0 && pvNativeRelative == NULL)
121 {
122 /* Caller already opened it, easy! */
123 pDir->hDir = (HANDLE)hRelativeDir;
124 rc = VINF_SUCCESS;
125 }
126 else
127 {
128 /*
129 * If we have to check for reparse points, this gets complicated!
130 */
131 static int volatile g_fReparsePoints = -1;
132 uint32_t fOptions = FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT;
133 int fReparsePoints = g_fReparsePoints;
134 if ( fReparsePoints != 0
135 && (pDir->fFlags & RTDIR_F_NO_FOLLOW)
136 && !pDir->fDirSlash)
137 fOptions |= FILE_OPEN_REPARSE_POINT;
138
139 ACCESS_MASK fDesiredAccess = FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | FILE_TRAVERSE | SYNCHRONIZE;
140 for (;;)
141 {
142 if (pvNativeRelative == NULL)
143 rc = RTNtPathOpenDir(pDir->pszPath,
144 fDesiredAccess,
145 FILE_SHARE_READ | FILE_SHARE_WRITE,
146 fOptions,
147 OBJ_CASE_INSENSITIVE,
148 &pDir->hDir,
149#ifdef IPRT_WITH_NT_PATH_PASSTHRU
150 &fObjDir
151#else
152 NULL
153#endif
154 );
155 else
156 rc = RTNtPathOpenDirEx((HANDLE)hRelativeDir,
157 (struct _UNICODE_STRING *)pvNativeRelative,
158 fDesiredAccess,
159 FILE_SHARE_READ | FILE_SHARE_WRITE,
160 fOptions,
161 OBJ_CASE_INSENSITIVE,
162 &pDir->hDir,
163#ifdef IPRT_WITH_NT_PATH_PASSTHRU
164 &fObjDir
165#else
166 NULL
167#endif
168 );
169 if ( rc == VERR_ACCESS_DENIED /* Seen with c:\windows\system32\com\dmp on w7 & w10 (admin mode). */
170 && (fDesiredAccess & FILE_TRAVERSE))
171 {
172 fDesiredAccess &= ~FILE_TRAVERSE;
173 continue;
174 }
175 if ( !(fOptions & FILE_OPEN_REPARSE_POINT)
176 || (rc != VINF_SUCCESS && rc != VERR_INVALID_PARAMETER) )
177 break;
178 if (rc == VINF_SUCCESS)
179 {
180 if (fReparsePoints == -1)
181 g_fReparsePoints = 1;
182
183 /*
184 * We now need to check if we opened a symbolic directory link (W2K+;
185 * on NT4 this is FileObjectIdInformation but it can't be queried and
186 * should thus fail with STATUS_INVALID_INFO_CLASS).
187 * (These can be enumerated, but contains only '.' and '..'.)
188 */
189 FILE_ATTRIBUTE_TAG_INFORMATION TagInfo = { 0, 0 };
190 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
191 NTSTATUS rcNt = NtQueryInformationFile(pDir->hDir, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation);
192 AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt));
193 if (!NT_SUCCESS(rcNt))
194 TagInfo.FileAttributes = TagInfo.ReparseTag = 0;
195 if (!(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
196 break;
197
198 NtClose(pDir->hDir);
199 pDir->hDir = RTNT_INVALID_HANDLE_VALUE;
200
201 if (TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK)
202 {
203 rc = VERR_IS_A_SYMLINK;
204 break;
205 }
206
207 /* Reparse point that isn't a symbolic link, try follow the reparsing. */
208 }
209 else if (fReparsePoints == -1)
210 g_fReparsePoints = fReparsePoints = 0;
211 fOptions &= ~FILE_OPEN_REPARSE_POINT;
212 }
213
214 }
215 if (RT_SUCCESS(rc))
216 {
217 /*
218 * Init data.
219 */
220 pDir->fDataUnread = false; /* spelling it out */
221 pDir->uDirDev = 0;
222#ifdef IPRT_WITH_NT_PATH_PASSTHRU
223 if (fObjDir)
224 pDir->enmInfoClass = FileMaximumInformation; /* object directory. */
225#endif
226 }
227 return rc;
228}
229
230
231RTDECL(int) RTDirClose(RTDIR hDir)
232{
233 PRTDIRINTERNAL pDir = hDir;
234
235 /*
236 * Validate input.
237 */
238 if (!pDir)
239 return VERR_INVALID_PARAMETER;
240 if (pDir->u32Magic != RTDIR_MAGIC)
241 {
242 AssertMsgFailed(("Invalid pDir=%p\n", pDir));
243 return VERR_INVALID_PARAMETER;
244 }
245
246 /*
247 * Close the handle.
248 */
249 pDir->u32Magic = ~RTDIR_MAGIC;
250 if (pDir->hDir != RTNT_INVALID_HANDLE_VALUE)
251 {
252 int rc = RTNtPathClose(pDir->hDir);
253 AssertRC(rc);
254 pDir->hDir = RTNT_INVALID_HANDLE_VALUE;
255 }
256 RTStrFree(pDir->pszName);
257 pDir->pszName = NULL;
258 RTUtf16Free(pDir->NtFilterStr.Buffer);
259 pDir->NtFilterStr.Buffer = NULL;
260 RTMemFree(pDir->pabBuffer);
261 pDir->pabBuffer = NULL;
262 RTMemFree(pDir);
263
264 return VINF_SUCCESS;
265}
266
267
268/**
269 * Checks the validity of the current record.
270 *
271 * @returns IPRT status code
272 * @param pThis The directory instance data.
273 */
274static int rtDirNtCheckRecord(PRTDIRINTERNAL pThis)
275{
276#if defined(RTDIR_NT_STRICT) || defined(RT_ARCH_X86)
277# ifdef IPRT_WITH_NT_PATH_PASSTHRU
278 if (pThis->enmInfoClass != FileMaximumInformation)
279# endif
280 {
281 uintptr_t uEndAddr;
282 if (pThis->enmInfoClass == FileIdBothDirectoryInformation)
283 uEndAddr = (uintptr_t)&pThis->uCurData.pBothId->FileName[0];
284 else
285 uEndAddr = (uintptr_t)&pThis->uCurData.pBoth->FileName[0];
286
287# ifdef RT_ARCH_X86
288 /* Workaround for NT 3.1 bug where FAT returns a too short buffer length.
289 Including all NT 3.x versions in case it bug was fixed till NT 4. */
290 uintptr_t const uEndBuffer = (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer];
291 if ( uEndAddr < uEndBuffer
292 && uEndAddr + pThis->uCurData.pBoth->FileNameLength <= uEndBuffer)
293 { /* likely */ }
294 else if ( ( g_enmWinVer == kRTWinOSType_NT310
295 || g_enmWinVer == kRTWinOSType_NT350 // not sure when it was fixed...
296 || g_enmWinVer == kRTWinOSType_NT351)
297 && pThis->enmInfoClass == FileBothDirectoryInformation)
298 {
299 size_t cbLeft = (uintptr_t)&pThis->pabBuffer[pThis->cbBufferAlloc] - (uintptr_t)pThis->uCurData.pBoth;
300 if ( cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName)
301 && pThis->uCurData.pBoth->FileNameLength > 0
302 && cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + pThis->uCurData.pBoth->FileNameLength)
303 {
304 pThis->cbBuffer = ((uintptr_t)&pThis->uCurData.pBoth->FileName[0] + pThis->uCurData.pBoth->FileNameLength)
305 - (uintptr_t)&pThis->pabBuffer[0];
306 }
307 }
308# endif
309
310# ifdef RTDIR_NT_STRICT
311 AssertReturn(uEndAddr < (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE);
312 AssertReturn(pThis->uCurData.pBoth->FileNameLength < _64K, VERR_FILENAME_TOO_LONG);
313 AssertReturn((pThis->uCurData.pBoth->FileNameLength & 1) == 0, VERR_IO_GEN_FAILURE);
314
315 uEndAddr += pThis->uCurData.pBoth->FileNameLength;
316 AssertReturn(uEndAddr <= (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE);
317
318 AssertReturn((unsigned)pThis->uCurData.pBoth->ShortNameLength <= sizeof(pThis->uCurData.pBoth->ShortName),
319 VERR_IO_GEN_FAILURE);
320# endif
321 }
322#else
323 RT_NOREF_PV(pThis);
324#endif
325
326 return VINF_SUCCESS;
327}
328
329
330/**
331 * Advances the buffer pointer.
332 *
333 * @param pThis The directory instance data.
334 */
335static int rtDirNtAdvanceBuffer(PRTDIRINTERNAL pThis)
336{
337 int rc;
338
339#ifdef IPRT_WITH_NT_PATH_PASSTHRU
340 if (pThis->enmInfoClass == FileMaximumInformation)
341 {
342 pThis->uCurData.pObjDir++;
343 pThis->fDataUnread = pThis->uCurData.pObjDir->Name.Length != 0;
344 return VINF_SUCCESS;
345 }
346#endif
347
348 pThis->fDataUnread = false;
349
350 uint32_t const offNext = pThis->uCurData.pBoth->NextEntryOffset;
351 if (offNext == 0)
352 return VINF_SUCCESS;
353
354#ifdef RTDIR_NT_STRICT
355 /* Make sure the next-record offset is beyond the current record. */
356 size_t cbRec;
357 if (pThis->enmInfoClass == FileIdBothDirectoryInformation)
358 cbRec = RT_UOFFSETOF(FILE_ID_BOTH_DIR_INFORMATION, FileName);
359 else
360 cbRec = RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName);
361 cbRec += pThis->uCurData.pBoth->FileNameLength;
362 AssertReturn(offNext >= cbRec, VERR_IO_GEN_FAILURE);
363#endif
364 pThis->uCurData.u += offNext;
365
366 rc = rtDirNtCheckRecord(pThis);
367 pThis->fDataUnread = RT_SUCCESS(rc);
368 return rc;
369}
370
371
372/**
373 * Fetches more data from the file system.
374 *
375 * @returns IPRT status code
376 * @param pThis The directory instance data.
377 */
378static int rtDirNtFetchMore(PRTDIRINTERNAL pThis)
379{
380 Assert(!pThis->fDataUnread);
381
382 /*
383 * Allocate the buffer the first time around.
384 * We do this in lazy fashion as some users of RTDirOpen will not actually
385 * list any files, just open it for various reasons.
386 *
387 * We also reduce the buffer size for networked devices as the windows 7-8.1,
388 * server 2012, ++ CIFS servers or/and IFSes screws up buffers larger than 64KB.
389 * There is an alternative hack below, btw. We'll leave both in for now.
390 */
391 bool fFirst = false;
392 if (!pThis->pabBuffer)
393 {
394 pThis->cbBufferAlloc = _256K;
395 if (true) /** @todo skip for known local devices, like the boot device? */
396 {
397 IO_STATUS_BLOCK Ios2 = RTNT_IO_STATUS_BLOCK_INITIALIZER;
398 FILE_FS_DEVICE_INFORMATION Info = { 0, 0 };
399 NTSTATUS rcNt2 = NtQueryVolumeInformationFile(pThis->hDir, &Ios2, &Info, sizeof(Info), FileFsDeviceInformation);
400 if ( !NT_SUCCESS(rcNt2)
401 || (Info.Characteristics & FILE_REMOTE_DEVICE)
402 || Info.DeviceType == FILE_DEVICE_NETWORK
403 || Info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM
404 || Info.DeviceType == FILE_DEVICE_NETWORK_REDIRECTOR
405 || Info.DeviceType == FILE_DEVICE_SMB)
406 pThis->cbBufferAlloc = _64K;
407 }
408
409 fFirst = false;
410 pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc);
411 if (!pThis->pabBuffer)
412 {
413 do
414 {
415 pThis->cbBufferAlloc /= 4;
416 pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc);
417 } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K);
418 if (!pThis->pabBuffer)
419 return VERR_NO_MEMORY;
420 }
421
422 /*
423 * Also try determining the device number.
424 */
425 PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pThis->pabBuffer;
426 pVolInfo->VolumeSerialNumber = 0;
427 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
428 NTSTATUS rcNt = NtQueryVolumeInformationFile(pThis->hDir, &Ios,
429 pVolInfo, RT_MIN(_2K, pThis->cbBufferAlloc),
430 FileFsVolumeInformation);
431 if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
432 pThis->uDirDev = pVolInfo->VolumeSerialNumber;
433 else
434 pThis->uDirDev = 0;
435 AssertCompile(sizeof(pThis->uDirDev) == sizeof(pVolInfo->VolumeSerialNumber));
436 /** @todo Grow RTDEV to 64-bit and add low dword of VolumeCreationTime to the top of uDirDev. */
437 }
438
439 /*
440 * Read more.
441 */
442 NTSTATUS rcNt;
443 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
444 if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0)
445 {
446#ifdef IPRT_WITH_NT_PATH_PASSTHRU
447 if (pThis->enmInfoClass == FileMaximumInformation)
448 {
449 Ios.Information = 0;
450 Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir,
451 pThis->pabBuffer,
452 pThis->cbBufferAlloc,
453 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
454 pThis->fRestartScan,
455 &pThis->uObjDirCtx,
456 (PULONG)&Ios.Information);
457 }
458 else
459#endif
460 rcNt = NtQueryDirectoryFile(pThis->hDir,
461 NULL /* Event */,
462 NULL /* ApcRoutine */,
463 NULL /* ApcContext */,
464 &Ios,
465 pThis->pabBuffer,
466 pThis->cbBufferAlloc,
467 pThis->enmInfoClass,
468 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
469 pThis->pNtFilterStr,
470 pThis->fRestartScan);
471 }
472 else
473 {
474 /*
475 * The first time around we have to figure which info class we can use
476 * as well as the right buffer size. We prefer an info class which
477 * gives us file IDs (Vista+ IIRC) and we prefer large buffers (for long
478 * ReFS file names and such), but we'll settle for whatever works...
479 *
480 * The windows 7 thru 8.1 CIFS servers have been observed to have
481 * trouble with large buffers, but weirdly only when listing large
482 * directories. Seems 0x10000 is the max. (Samba does not exhibit
483 * these problems, of course.)
484 *
485 * This complicates things. The buffer size issues causes an
486 * STATUS_INVALID_PARAMETER error. Now, you would expect the lack of
487 * FileIdBothDirectoryInformation support to return
488 * STATUS_INVALID_INFO_CLASS, but I'm not entirely sure if we can 100%
489 * depend on third IFSs to get that right. Nor, am I entirely confident
490 * that we can depend on them to check the class before the buffer size.
491 *
492 * Thus the mess.
493 */
494 if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */)
495 pThis->enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */
496 else
497 pThis->enmInfoClass = FileBothDirectoryInformation;
498 rcNt = NtQueryDirectoryFile(pThis->hDir,
499 NULL /* Event */,
500 NULL /* ApcRoutine */,
501 NULL /* ApcContext */,
502 &Ios,
503 pThis->pabBuffer,
504 pThis->cbBufferAlloc,
505 pThis->enmInfoClass,
506 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
507 pThis->pNtFilterStr,
508 pThis->fRestartScan);
509 if (NT_SUCCESS(rcNt))
510 { /* likely */ }
511 else
512 {
513 bool fRestartScan = pThis->fRestartScan;
514 for (unsigned iRetry = 0; iRetry < 2; iRetry++)
515 {
516 if ( rcNt == STATUS_INVALID_INFO_CLASS
517 || rcNt == STATUS_INVALID_PARAMETER_8
518 || iRetry != 0)
519 pThis->enmInfoClass = FileBothDirectoryInformation;
520
521 uint32_t cbBuffer = pThis->cbBufferAlloc;
522 if ( rcNt == STATUS_INVALID_PARAMETER
523 || rcNt == STATUS_INVALID_PARAMETER_7
524 || rcNt == STATUS_INVALID_NETWORK_RESPONSE
525 || iRetry != 0)
526 {
527 cbBuffer = RT_MIN(cbBuffer / 2, _64K);
528 fRestartScan = true;
529 }
530
531 for (;;)
532 {
533 rcNt = NtQueryDirectoryFile(pThis->hDir,
534 NULL /* Event */,
535 NULL /* ApcRoutine */,
536 NULL /* ApcContext */,
537 &Ios,
538 pThis->pabBuffer,
539 cbBuffer,
540 pThis->enmInfoClass,
541 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
542 pThis->pNtFilterStr,
543 fRestartScan);
544 if ( NT_SUCCESS(rcNt)
545 || cbBuffer == pThis->cbBufferAlloc
546 || cbBuffer <= sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260)
547 break;
548
549 /* Reduce the buffer size agressivly and try again. We fall back to
550 FindFirstFile values for the final lap. This means we'll do 4 rounds
551 with the current initial buffer size (64KB, 8KB, 1KB, 0x278/0x268). */
552 cbBuffer /= 8;
553 if (cbBuffer < 1024)
554 cbBuffer = pThis->enmInfoClass == FileIdBothDirectoryInformation
555 ? sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260
556 : sizeof(*pThis->uCurData.pBoth) + sizeof(WCHAR) * 260;
557 }
558 if (NT_SUCCESS(rcNt))
559 {
560 pThis->cbBufferAlloc = cbBuffer;
561 break;
562 }
563 }
564 }
565 }
566 if (!NT_SUCCESS(rcNt))
567 {
568 /* Note! VBoxSVR and CIFS file systems both ends up with STATUS_NO_SUCH_FILE here instead of STATUS_NO_MORE_FILES. */
569 if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE)
570 return VERR_NO_MORE_FILES;
571 return RTErrConvertFromNtStatus(rcNt);
572 }
573 pThis->fRestartScan = false;
574 AssertMsg( Ios.Information
575 > (pThis->enmInfoClass == FileMaximumInformation ? sizeof(*pThis->uCurData.pObjDir) : sizeof(*pThis->uCurData.pBoth)),
576 ("Ios.Information=%#x\n", Ios.Information));
577
578 /*
579 * Set up the data members.
580 */
581 pThis->uCurData.u = (uintptr_t)pThis->pabBuffer;
582 pThis->cbBuffer = Ios.Information;
583
584 int rc = rtDirNtCheckRecord(pThis);
585 pThis->fDataUnread = RT_SUCCESS(rc);
586
587 return rc;
588}
589
590
591/**
592 * Converts the name from UTF-16 to UTF-8.
593 *
594 * Fortunately, the names are relative to the directory, so we won't have to do
595 * any sweaty path style coversion. :-)
596 *
597 * @returns IPRT status code
598 * @param pThis The directory instance data.
599 * @param cbName The file name length in bytes.
600 * @param pwsName The file name, not terminated.
601 */
602static int rtDirNtConvertName(PRTDIRINTERNAL pThis, uint32_t cbName, PCRTUTF16 pwsName)
603{
604 int rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName);
605 if (RT_SUCCESS(rc))
606 {
607 if (!pThis->cbNameAlloc)
608 pThis->cbNameAlloc = pThis->cchName + 1;
609 }
610 else if (rc == VERR_BUFFER_OVERFLOW)
611 {
612 RTStrFree(pThis->pszName);
613 pThis->pszName = NULL;
614 pThis->cbNameAlloc = 0;
615
616 rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName);
617 if (RT_SUCCESS(rc))
618 pThis->cbNameAlloc = pThis->cchName + 1;
619 }
620 Assert(RT_SUCCESS(rc) ? pThis->pszName != NULL : pThis->pszName == NULL);
621 return rc;
622}
623
624
625/**
626 * Converts the name of the current record.
627 *
628 * @returns IPRT status code.
629 * @param pThis The directory instance data.
630 */
631static int rtDirNtConvertCurName(PRTDIRINTERNAL pThis)
632{
633 switch (pThis->enmInfoClass)
634 {
635 case FileIdBothDirectoryInformation:
636 return rtDirNtConvertName(pThis, pThis->uCurData.pBothId->FileNameLength, pThis->uCurData.pBothId->FileName);
637 case FileBothDirectoryInformation:
638 return rtDirNtConvertName(pThis, pThis->uCurData.pBoth->FileNameLength, pThis->uCurData.pBoth->FileName);
639#ifdef IPRT_WITH_NT_PATH_PASSTHRU
640 case FileMaximumInformation:
641 return rtDirNtConvertName(pThis, pThis->uCurData.pObjDir->Name.Length, pThis->uCurData.pObjDir->Name.Buffer);
642#endif
643
644 default:
645 AssertFailedReturn(VERR_INTERNAL_ERROR_3);
646 }
647}
648
649
650RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry)
651{
652 PRTDIRINTERNAL pDir = hDir;
653 int rc;
654
655 /*
656 * Validate input.
657 */
658 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
659 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
660 AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);
661 size_t cbDirEntry = sizeof(*pDirEntry);
662 if (pcbDirEntry)
663 {
664 cbDirEntry = *pcbDirEntry;
665 AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRY, szName[2]),
666 ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRY, szName[2])),
667 VERR_INVALID_PARAMETER);
668 }
669
670 /*
671 * Fetch data?
672 */
673 if (!pDir->fDataUnread)
674 {
675 rc = rtDirNtFetchMore(pDir);
676 if (RT_FAILURE(rc))
677 return rc;
678 }
679
680 /*
681 * Convert the filename to UTF-8.
682 */
683 rc = rtDirNtConvertCurName(pDir);
684 if (RT_FAILURE(rc))
685 return rc;
686
687 /*
688 * Check if we've got enough space to return the data.
689 */
690 const char *pszName = pDir->pszName;
691 const size_t cchName = pDir->cchName;
692 const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName;
693 if (pcbDirEntry)
694 *pcbDirEntry = cbRequired;
695 if (cbRequired > cbDirEntry)
696 return VERR_BUFFER_OVERFLOW;
697
698 /*
699 * Setup the returned data.
700 */
701 pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName);
702 memcpy(pDirEntry->szName, pszName, cchName + 1);
703
704 pDirEntry->INodeId = pDir->enmInfoClass == FileIdBothDirectoryInformation
705 ? pDir->uCurData.pBothId->FileId.QuadPart : 0;
706
707#ifdef IPRT_WITH_NT_PATH_PASSTHRU
708 if (pDir->enmInfoClass != FileMaximumInformation)
709#endif
710 {
711 switch ( pDir->uCurData.pBoth->FileAttributes
712 & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
713 {
714 default:
715 AssertFailed();
716 RT_FALL_THRU();
717 case 0:
718 pDirEntry->enmType = RTDIRENTRYTYPE_FILE;
719 break;
720
721 case FILE_ATTRIBUTE_DIRECTORY:
722 pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY;
723 break;
724
725 case FILE_ATTRIBUTE_REPARSE_POINT:
726 case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY:
727 /* EaSize is here reused for returning the repharse tag value. */
728 if (pDir->uCurData.pBoth->EaSize == IO_REPARSE_TAG_SYMLINK)
729 pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK;
730 break;
731 }
732 }
733#ifdef IPRT_WITH_NT_PATH_PASSTHRU
734 else
735 {
736 pDirEntry->enmType = RTDIRENTRYTYPE_UNKNOWN;
737 if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
738 RT_STR_TUPLE("Directory")))
739 pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY;
740 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
741 RT_STR_TUPLE("SymbolicLink")))
742 pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK;
743 }
744#endif
745
746 return rtDirNtAdvanceBuffer(pDir);
747}
748
749
750RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
751 RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags)
752{
753 PRTDIRINTERNAL pDir = hDir;
754 int rc;
755
756 /*
757 * Validate input.
758 */
759 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
760 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
761 AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);
762
763 AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
764 VERR_INVALID_PARAMETER);
765 AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
766
767 size_t cbDirEntry = sizeof(*pDirEntry);
768 if (pcbDirEntry)
769 {
770 cbDirEntry = *pcbDirEntry;
771 AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]),
772 ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])),
773 VERR_INVALID_PARAMETER);
774 }
775
776 /*
777 * Fetch data?
778 */
779 if (!pDir->fDataUnread)
780 {
781 rc = rtDirNtFetchMore(pDir);
782 if (RT_FAILURE(rc))
783 return rc;
784 }
785
786 /*
787 * Convert the filename to UTF-8.
788 */
789 rc = rtDirNtConvertCurName(pDir);
790 if (RT_FAILURE(rc))
791 return rc;
792
793 /*
794 * Check if we've got enough space to return the data.
795 */
796 const char *pszName = pDir->pszName;
797 const size_t cchName = pDir->cchName;
798 const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName;
799 if (pcbDirEntry)
800 *pcbDirEntry = cbRequired;
801 if (cbRequired > cbDirEntry)
802 return VERR_BUFFER_OVERFLOW;
803
804 /*
805 * Setup the returned data.
806 */
807 PFILE_BOTH_DIR_INFORMATION pBoth = pDir->uCurData.pBoth;
808
809 pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName);
810 memcpy(pDirEntry->szName, pszName, cchName + 1);
811 memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName));
812#ifdef IPRT_WITH_NT_PATH_PASSTHRU
813 if (pDir->enmInfoClass != FileMaximumInformation)
814#endif
815 {
816 uint8_t cbShort = pBoth->ShortNameLength;
817 if (cbShort > 0)
818 {
819 AssertStmt(cbShort < sizeof(pDirEntry->wszShortName), cbShort = sizeof(pDirEntry->wszShortName) - 2);
820 memcpy(pDirEntry->wszShortName, pBoth->ShortName, cbShort);
821 pDirEntry->cwcShortName = cbShort / 2;
822 }
823 else
824 pDirEntry->cwcShortName = 0;
825
826 pDirEntry->Info.cbObject = pBoth->EndOfFile.QuadPart;
827 pDirEntry->Info.cbAllocated = pBoth->AllocationSize.QuadPart;
828
829 Assert(sizeof(uint64_t) == sizeof(pBoth->CreationTime));
830 RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, pBoth->CreationTime.QuadPart);
831 RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, pBoth->LastAccessTime.QuadPart);
832 RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, pBoth->LastWriteTime.QuadPart);
833 RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, pBoth->ChangeTime.QuadPart);
834
835 pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pBoth->FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
836 pszName, cchName, pBoth->EaSize, 0);
837 }
838#ifdef IPRT_WITH_NT_PATH_PASSTHRU
839 else
840 {
841 pDirEntry->cwcShortName = 0;
842 pDirEntry->Info.cbObject = 0;
843 pDirEntry->Info.cbAllocated = 0;
844 RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, 0);
845 RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, 0);
846 RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, 0);
847 RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, 0);
848
849 if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
850 RT_STR_TUPLE("Directory")))
851 pDirEntry->Info.Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777;
852 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
853 RT_STR_TUPLE("SymbolicLink")))
854 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777;
855 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
856 RT_STR_TUPLE("Device")))
857 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666;
858 else
859 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666;
860 }
861#endif
862
863 /*
864 * Requested attributes (we cannot provide anything actually).
865 */
866 switch (enmAdditionalAttribs)
867 {
868 case RTFSOBJATTRADD_EASIZE:
869 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
870#ifdef IPRT_WITH_NT_PATH_PASSTHRU
871 if (pDir->enmInfoClass == FileMaximumInformation)
872 pDirEntry->Info.Attr.u.EASize.cb = 0;
873 else
874#endif
875 pDirEntry->Info.Attr.u.EASize.cb = pBoth->EaSize;
876 break;
877
878 case RTFSOBJATTRADD_UNIX:
879 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
880 pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID;
881 pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID;
882 pDirEntry->Info.Attr.u.Unix.cHardlinks = 1;
883 pDirEntry->Info.Attr.u.Unix.INodeIdDevice = pDir->uDirDev;
884 pDirEntry->Info.Attr.u.Unix.INodeId = 0;
885 if ( pDir->enmInfoClass == FileIdBothDirectoryInformation
886 && pDir->uCurData.pBothId->FileId.QuadPart != UINT64_MAX)
887 pDirEntry->Info.Attr.u.Unix.INodeId = pDir->uCurData.pBothId->FileId.QuadPart;
888 pDirEntry->Info.Attr.u.Unix.fFlags = 0;
889 pDirEntry->Info.Attr.u.Unix.GenerationId = 0;
890 pDirEntry->Info.Attr.u.Unix.Device = 0;
891 break;
892
893 case RTFSOBJATTRADD_NOTHING:
894 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING;
895 break;
896
897 case RTFSOBJATTRADD_UNIX_OWNER:
898 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
899 pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID;
900 pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */
901 break;
902
903 case RTFSOBJATTRADD_UNIX_GROUP:
904 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
905 pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID;
906 pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0';
907 break;
908
909 default:
910 AssertMsgFailed(("Impossible!\n"));
911 return VERR_INTERNAL_ERROR;
912 }
913
914 /*
915 * Follow links if requested.
916 */
917 if ( (fFlags & RTPATH_F_FOLLOW_LINK)
918 && RTFS_IS_SYMLINK(fFlags))
919 {
920 /** @todo Symlinks: Find[First|Next]FileW will return info about
921 the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */
922 }
923
924 /*
925 * Finally advance the buffer.
926 */
927 return rtDirNtAdvanceBuffer(pDir);
928}
929
930
931RTDECL(int) RTDirRewind(RTDIR hDir)
932{
933 /*
934 * Validate and digest input.
935 */
936 PRTDIRINTERNAL pThis = hDir;
937 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
938 AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
939
940 /*
941 * The work is done on the next call to rtDirNtFetchMore.
942 */
943 pThis->fRestartScan = true;
944 pThis->fDataUnread = false;
945
946 return VINF_SUCCESS;
947}
948
949
950RTR3DECL(int) RTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs)
951{
952 PRTDIRINTERNAL pDir = hDir;
953 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
954 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
955 AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
956 VERR_INVALID_PARAMETER);
957
958 if (pDir->enmInfoClass == FileMaximumInformation)
959 {
960 /*
961 * Directory object (see similar code above and rtPathNtQueryInfoInDirectoryObject).
962 */
963 pObjInfo->cbObject = 0;
964 pObjInfo->cbAllocated = 0;
965 RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0);
966 RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0);
967 RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0);
968 RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0);
969 pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777;
970 pObjInfo->Attr.enmAdditional = enmAdditionalAttribs;
971 switch (enmAdditionalAttribs)
972 {
973 case RTFSOBJATTRADD_NOTHING:
974 case RTFSOBJATTRADD_UNIX:
975 pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
976 pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
977 pObjInfo->Attr.u.Unix.cHardlinks = 1;
978 pObjInfo->Attr.u.Unix.INodeIdDevice = pDir->uDirDev;
979 pObjInfo->Attr.u.Unix.INodeId = 0;
980 pObjInfo->Attr.u.Unix.fFlags = 0;
981 pObjInfo->Attr.u.Unix.GenerationId = 0;
982 pObjInfo->Attr.u.Unix.Device = 0;
983 break;
984
985 case RTFSOBJATTRADD_EASIZE:
986 pObjInfo->Attr.u.EASize.cb = 0;
987 break;
988
989 case RTFSOBJATTRADD_UNIX_OWNER:
990 pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
991 pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
992 pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */
993 break;
994
995 case RTFSOBJATTRADD_UNIX_GROUP:
996 pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
997 pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
998 pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
999 break;
1000
1001 default:
1002 AssertMsgFailed(("Impossible!\n"));
1003 return VERR_INTERNAL_ERROR_2;
1004 }
1005 return VINF_SUCCESS;
1006 }
1007
1008 /*
1009 * Regular directory file.
1010 */
1011 uint8_t abBuf[_2K];
1012 return rtPathNtQueryInfoFromHandle(pDir->hDir, abBuf, sizeof(abBuf), pObjInfo, enmAdditionalAttribs, "", 0);
1013}
1014
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette