VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 17 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 39.1 KB
Line 
1/* $Id: direnum-r3-nt.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * IPRT - Directory Enumeration, Native NT.
4 */
5
6/*
7 * Copyright (C) 2006-2023 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.
185 * (These can be enumerated, but contains only '.' and '..'.)
186 */
187 FILE_ATTRIBUTE_TAG_INFORMATION TagInfo = { 0, 0 };
188 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
189 NTSTATUS rcNt = NtQueryInformationFile(pDir->hDir, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation);
190 AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt));
191 if (!NT_SUCCESS(rcNt))
192 TagInfo.FileAttributes = TagInfo.ReparseTag = 0;
193 if (!(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
194 break;
195
196 NtClose(pDir->hDir);
197 pDir->hDir = RTNT_INVALID_HANDLE_VALUE;
198
199 if (TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK)
200 {
201 rc = VERR_IS_A_SYMLINK;
202 break;
203 }
204
205 /* Reparse point that isn't a symbolic link, try follow the reparsing. */
206 }
207 else if (fReparsePoints == -1)
208 g_fReparsePoints = fReparsePoints = 0;
209 fOptions &= ~FILE_OPEN_REPARSE_POINT;
210 }
211
212 }
213 if (RT_SUCCESS(rc))
214 {
215 /*
216 * Init data.
217 */
218 pDir->fDataUnread = false; /* spelling it out */
219 pDir->uDirDev = 0;
220#ifdef IPRT_WITH_NT_PATH_PASSTHRU
221 if (fObjDir)
222 pDir->enmInfoClass = FileMaximumInformation; /* object directory. */
223#endif
224 }
225 return rc;
226}
227
228
229RTDECL(int) RTDirClose(RTDIR hDir)
230{
231 PRTDIRINTERNAL pDir = hDir;
232
233 /*
234 * Validate input.
235 */
236 if (!pDir)
237 return VERR_INVALID_PARAMETER;
238 if (pDir->u32Magic != RTDIR_MAGIC)
239 {
240 AssertMsgFailed(("Invalid pDir=%p\n", pDir));
241 return VERR_INVALID_PARAMETER;
242 }
243
244 /*
245 * Close the handle.
246 */
247 pDir->u32Magic = ~RTDIR_MAGIC;
248 if (pDir->hDir != RTNT_INVALID_HANDLE_VALUE)
249 {
250 int rc = RTNtPathClose(pDir->hDir);
251 AssertRC(rc);
252 pDir->hDir = RTNT_INVALID_HANDLE_VALUE;
253 }
254 RTStrFree(pDir->pszName);
255 pDir->pszName = NULL;
256 RTUtf16Free(pDir->NtFilterStr.Buffer);
257 pDir->NtFilterStr.Buffer = NULL;
258 RTMemFree(pDir->pabBuffer);
259 pDir->pabBuffer = NULL;
260 RTMemFree(pDir);
261
262 return VINF_SUCCESS;
263}
264
265
266/**
267 * Checks the validity of the current record.
268 *
269 * @returns IPRT status code
270 * @param pThis The directory instance data.
271 */
272static int rtDirNtCheckRecord(PRTDIRINTERNAL pThis)
273{
274#if defined(RTDIR_NT_STRICT) || defined(RT_ARCH_X86)
275# ifdef IPRT_WITH_NT_PATH_PASSTHRU
276 if (pThis->enmInfoClass != FileMaximumInformation)
277# endif
278 {
279 uintptr_t uEndAddr;
280 if (pThis->enmInfoClass == FileIdBothDirectoryInformation)
281 uEndAddr = (uintptr_t)&pThis->uCurData.pBothId->FileName[0];
282 else
283 uEndAddr = (uintptr_t)&pThis->uCurData.pBoth->FileName[0];
284
285# ifdef RT_ARCH_X86
286 /* Workaround for NT 3.1 bug where FAT returns a too short buffer length.
287 Including all NT 3.x versions in case it bug was fixed till NT 4. */
288 uintptr_t const uEndBuffer = (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer];
289 if ( uEndAddr < uEndBuffer
290 && uEndAddr + pThis->uCurData.pBoth->FileNameLength <= uEndBuffer)
291 { /* likely */ }
292 else if ( ( g_enmWinVer == kRTWinOSType_NT310
293 || g_enmWinVer == kRTWinOSType_NT350 // not sure when it was fixed...
294 || g_enmWinVer == kRTWinOSType_NT351)
295 && pThis->enmInfoClass == FileBothDirectoryInformation)
296 {
297 size_t cbLeft = (uintptr_t)&pThis->pabBuffer[pThis->cbBufferAlloc] - (uintptr_t)pThis->uCurData.pBoth;
298 if ( cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName)
299 && pThis->uCurData.pBoth->FileNameLength > 0
300 && cbLeft >= RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName) + pThis->uCurData.pBoth->FileNameLength)
301 {
302 pThis->cbBuffer = ((uintptr_t)&pThis->uCurData.pBoth->FileName[0] + pThis->uCurData.pBoth->FileNameLength)
303 - (uintptr_t)&pThis->pabBuffer[0];
304 }
305 }
306# endif
307
308# ifdef RTDIR_NT_STRICT
309 AssertReturn(uEndAddr < (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE);
310 AssertReturn(pThis->uCurData.pBoth->FileNameLength < _64K, VERR_FILENAME_TOO_LONG);
311 AssertReturn((pThis->uCurData.pBoth->FileNameLength & 1) == 0, VERR_IO_GEN_FAILURE);
312
313 uEndAddr += pThis->uCurData.pBoth->FileNameLength;
314 AssertReturn(uEndAddr <= (uintptr_t)&pThis->pabBuffer[pThis->cbBuffer], VERR_IO_GEN_FAILURE);
315
316 AssertReturn((unsigned)pThis->uCurData.pBoth->ShortNameLength <= sizeof(pThis->uCurData.pBoth->ShortName),
317 VERR_IO_GEN_FAILURE);
318# endif
319 }
320#else
321 RT_NOREF_PV(pThis);
322#endif
323
324 return VINF_SUCCESS;
325}
326
327
328/**
329 * Advances the buffer pointer.
330 *
331 * @param pThis The directory instance data.
332 */
333static int rtDirNtAdvanceBuffer(PRTDIRINTERNAL pThis)
334{
335 int rc;
336
337#ifdef IPRT_WITH_NT_PATH_PASSTHRU
338 if (pThis->enmInfoClass == FileMaximumInformation)
339 {
340 pThis->uCurData.pObjDir++;
341 pThis->fDataUnread = pThis->uCurData.pObjDir->Name.Length != 0;
342 return VINF_SUCCESS;
343 }
344#endif
345
346 pThis->fDataUnread = false;
347
348 uint32_t const offNext = pThis->uCurData.pBoth->NextEntryOffset;
349 if (offNext == 0)
350 return VINF_SUCCESS;
351
352#ifdef RTDIR_NT_STRICT
353 /* Make sure the next-record offset is beyond the current record. */
354 size_t cbRec;
355 if (pThis->enmInfoClass == FileIdBothDirectoryInformation)
356 cbRec = RT_UOFFSETOF(FILE_ID_BOTH_DIR_INFORMATION, FileName);
357 else
358 cbRec = RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName);
359 cbRec += pThis->uCurData.pBoth->FileNameLength;
360 AssertReturn(offNext >= cbRec, VERR_IO_GEN_FAILURE);
361#endif
362 pThis->uCurData.u += offNext;
363
364 rc = rtDirNtCheckRecord(pThis);
365 pThis->fDataUnread = RT_SUCCESS(rc);
366 return rc;
367}
368
369
370/**
371 * Fetches more data from the file system.
372 *
373 * @returns IPRT status code
374 * @param pThis The directory instance data.
375 */
376static int rtDirNtFetchMore(PRTDIRINTERNAL pThis)
377{
378 Assert(!pThis->fDataUnread);
379
380 /*
381 * Allocate the buffer the first time around.
382 * We do this in lazy fashion as some users of RTDirOpen will not actually
383 * list any files, just open it for various reasons.
384 *
385 * We also reduce the buffer size for networked devices as the windows 7-8.1,
386 * server 2012, ++ CIFS servers or/and IFSes screws up buffers larger than 64KB.
387 * There is an alternative hack below, btw. We'll leave both in for now.
388 */
389 bool fFirst = false;
390 if (!pThis->pabBuffer)
391 {
392 pThis->cbBufferAlloc = _256K;
393 if (true) /** @todo skip for known local devices, like the boot device? */
394 {
395 IO_STATUS_BLOCK Ios2 = RTNT_IO_STATUS_BLOCK_INITIALIZER;
396 FILE_FS_DEVICE_INFORMATION Info = { 0, 0 };
397 NTSTATUS rcNt2 = NtQueryVolumeInformationFile(pThis->hDir, &Ios2, &Info, sizeof(Info), FileFsDeviceInformation);
398 if ( !NT_SUCCESS(rcNt2)
399 || (Info.Characteristics & FILE_REMOTE_DEVICE)
400 || Info.DeviceType == FILE_DEVICE_NETWORK
401 || Info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM
402 || Info.DeviceType == FILE_DEVICE_NETWORK_REDIRECTOR
403 || Info.DeviceType == FILE_DEVICE_SMB)
404 pThis->cbBufferAlloc = _64K;
405 }
406
407 fFirst = false;
408 pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc);
409 if (!pThis->pabBuffer)
410 {
411 do
412 {
413 pThis->cbBufferAlloc /= 4;
414 pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc);
415 } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K);
416 if (!pThis->pabBuffer)
417 return VERR_NO_MEMORY;
418 }
419
420 /*
421 * Also try determining the device number.
422 */
423 PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pThis->pabBuffer;
424 pVolInfo->VolumeSerialNumber = 0;
425 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
426 NTSTATUS rcNt = NtQueryVolumeInformationFile(pThis->hDir, &Ios,
427 pVolInfo, RT_MIN(_2K, pThis->cbBufferAlloc),
428 FileFsVolumeInformation);
429 if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status))
430 pThis->uDirDev = pVolInfo->VolumeSerialNumber;
431 else
432 pThis->uDirDev = 0;
433 AssertCompile(sizeof(pThis->uDirDev) == sizeof(pVolInfo->VolumeSerialNumber));
434 /** @todo Grow RTDEV to 64-bit and add low dword of VolumeCreationTime to the top of uDirDev. */
435 }
436
437 /*
438 * Read more.
439 */
440 NTSTATUS rcNt;
441 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
442 if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0)
443 {
444#ifdef IPRT_WITH_NT_PATH_PASSTHRU
445 if (pThis->enmInfoClass == FileMaximumInformation)
446 {
447 Ios.Information = 0;
448 Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir,
449 pThis->pabBuffer,
450 pThis->cbBufferAlloc,
451 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
452 pThis->fRestartScan,
453 &pThis->uObjDirCtx,
454 (PULONG)&Ios.Information);
455 }
456 else
457#endif
458 rcNt = NtQueryDirectoryFile(pThis->hDir,
459 NULL /* Event */,
460 NULL /* ApcRoutine */,
461 NULL /* ApcContext */,
462 &Ios,
463 pThis->pabBuffer,
464 pThis->cbBufferAlloc,
465 pThis->enmInfoClass,
466 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
467 pThis->pNtFilterStr,
468 pThis->fRestartScan);
469 }
470 else
471 {
472 /*
473 * The first time around we have to figure which info class we can use
474 * as well as the right buffer size. We prefer an info class which
475 * gives us file IDs (Vista+ IIRC) and we prefer large buffers (for long
476 * ReFS file names and such), but we'll settle for whatever works...
477 *
478 * The windows 7 thru 8.1 CIFS servers have been observed to have
479 * trouble with large buffers, but weirdly only when listing large
480 * directories. Seems 0x10000 is the max. (Samba does not exhibit
481 * these problems, of course.)
482 *
483 * This complicates things. The buffer size issues causes an
484 * STATUS_INVALID_PARAMETER error. Now, you would expect the lack of
485 * FileIdBothDirectoryInformation support to return
486 * STATUS_INVALID_INFO_CLASS, but I'm not entirely sure if we can 100%
487 * depend on third IFSs to get that right. Nor, am I entirely confident
488 * that we can depend on them to check the class before the buffer size.
489 *
490 * Thus the mess.
491 */
492 if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */)
493 pThis->enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */
494 else
495 pThis->enmInfoClass = FileBothDirectoryInformation;
496 rcNt = NtQueryDirectoryFile(pThis->hDir,
497 NULL /* Event */,
498 NULL /* ApcRoutine */,
499 NULL /* ApcContext */,
500 &Ios,
501 pThis->pabBuffer,
502 pThis->cbBufferAlloc,
503 pThis->enmInfoClass,
504 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
505 pThis->pNtFilterStr,
506 pThis->fRestartScan);
507 if (NT_SUCCESS(rcNt))
508 { /* likely */ }
509 else
510 {
511 bool fRestartScan = pThis->fRestartScan;
512 for (unsigned iRetry = 0; iRetry < 2; iRetry++)
513 {
514 if ( rcNt == STATUS_INVALID_INFO_CLASS
515 || rcNt == STATUS_INVALID_PARAMETER_8
516 || iRetry != 0)
517 pThis->enmInfoClass = FileBothDirectoryInformation;
518
519 uint32_t cbBuffer = pThis->cbBufferAlloc;
520 if ( rcNt == STATUS_INVALID_PARAMETER
521 || rcNt == STATUS_INVALID_PARAMETER_7
522 || rcNt == STATUS_INVALID_NETWORK_RESPONSE
523 || iRetry != 0)
524 {
525 cbBuffer = RT_MIN(cbBuffer / 2, _64K);
526 fRestartScan = true;
527 }
528
529 for (;;)
530 {
531 rcNt = NtQueryDirectoryFile(pThis->hDir,
532 NULL /* Event */,
533 NULL /* ApcRoutine */,
534 NULL /* ApcContext */,
535 &Ios,
536 pThis->pabBuffer,
537 cbBuffer,
538 pThis->enmInfoClass,
539 RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */,
540 pThis->pNtFilterStr,
541 fRestartScan);
542 if ( NT_SUCCESS(rcNt)
543 || cbBuffer == pThis->cbBufferAlloc
544 || cbBuffer <= sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260)
545 break;
546
547 /* Reduce the buffer size agressivly and try again. We fall back to
548 FindFirstFile values for the final lap. This means we'll do 4 rounds
549 with the current initial buffer size (64KB, 8KB, 1KB, 0x278/0x268). */
550 cbBuffer /= 8;
551 if (cbBuffer < 1024)
552 cbBuffer = pThis->enmInfoClass == FileIdBothDirectoryInformation
553 ? sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260
554 : sizeof(*pThis->uCurData.pBoth) + sizeof(WCHAR) * 260;
555 }
556 if (NT_SUCCESS(rcNt))
557 {
558 pThis->cbBufferAlloc = cbBuffer;
559 break;
560 }
561 }
562 }
563 }
564 if (!NT_SUCCESS(rcNt))
565 {
566 /* Note! VBoxSVR and CIFS file systems both ends up with STATUS_NO_SUCH_FILE here instead of STATUS_NO_MORE_FILES. */
567 if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE)
568 return VERR_NO_MORE_FILES;
569 return RTErrConvertFromNtStatus(rcNt);
570 }
571 pThis->fRestartScan = false;
572 AssertMsg( Ios.Information
573 > (pThis->enmInfoClass == FileMaximumInformation ? sizeof(*pThis->uCurData.pObjDir) : sizeof(*pThis->uCurData.pBoth)),
574 ("Ios.Information=%#x\n", Ios.Information));
575
576 /*
577 * Set up the data members.
578 */
579 pThis->uCurData.u = (uintptr_t)pThis->pabBuffer;
580 pThis->cbBuffer = Ios.Information;
581
582 int rc = rtDirNtCheckRecord(pThis);
583 pThis->fDataUnread = RT_SUCCESS(rc);
584
585 return rc;
586}
587
588
589/**
590 * Converts the name from UTF-16 to UTF-8.
591 *
592 * Fortunately, the names are relative to the directory, so we won't have to do
593 * any sweaty path style coversion. :-)
594 *
595 * @returns IPRT status code
596 * @param pThis The directory instance data.
597 * @param cbName The file name length in bytes.
598 * @param pwsName The file name, not terminated.
599 */
600static int rtDirNtConvertName(PRTDIRINTERNAL pThis, uint32_t cbName, PCRTUTF16 pwsName)
601{
602 int rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName);
603 if (RT_SUCCESS(rc))
604 {
605 if (!pThis->cbNameAlloc)
606 pThis->cbNameAlloc = pThis->cchName + 1;
607 }
608 else if (rc == VERR_BUFFER_OVERFLOW)
609 {
610 RTStrFree(pThis->pszName);
611 pThis->pszName = NULL;
612 pThis->cbNameAlloc = 0;
613
614 rc = RTUtf16ToUtf8Ex(pwsName, cbName / 2, &pThis->pszName, pThis->cbNameAlloc, &pThis->cchName);
615 if (RT_SUCCESS(rc))
616 pThis->cbNameAlloc = pThis->cchName + 1;
617 }
618 Assert(RT_SUCCESS(rc) ? pThis->pszName != NULL : pThis->pszName == NULL);
619 return rc;
620}
621
622
623/**
624 * Converts the name of the current record.
625 *
626 * @returns IPRT status code.
627 * @param pThis The directory instance data.
628 */
629static int rtDirNtConvertCurName(PRTDIRINTERNAL pThis)
630{
631 switch (pThis->enmInfoClass)
632 {
633 case FileIdBothDirectoryInformation:
634 return rtDirNtConvertName(pThis, pThis->uCurData.pBothId->FileNameLength, pThis->uCurData.pBothId->FileName);
635 case FileBothDirectoryInformation:
636 return rtDirNtConvertName(pThis, pThis->uCurData.pBoth->FileNameLength, pThis->uCurData.pBoth->FileName);
637#ifdef IPRT_WITH_NT_PATH_PASSTHRU
638 case FileMaximumInformation:
639 return rtDirNtConvertName(pThis, pThis->uCurData.pObjDir->Name.Length, pThis->uCurData.pObjDir->Name.Buffer);
640#endif
641
642 default:
643 AssertFailedReturn(VERR_INTERNAL_ERROR_3);
644 }
645}
646
647
648RTDECL(int) RTDirRead(RTDIR hDir, PRTDIRENTRY pDirEntry, size_t *pcbDirEntry)
649{
650 PRTDIRINTERNAL pDir = hDir;
651 int rc;
652
653 /*
654 * Validate input.
655 */
656 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
657 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
658 AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);
659 size_t cbDirEntry = sizeof(*pDirEntry);
660 if (pcbDirEntry)
661 {
662 cbDirEntry = *pcbDirEntry;
663 AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRY, szName[2]),
664 ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRY, szName[2])),
665 VERR_INVALID_PARAMETER);
666 }
667
668 /*
669 * Fetch data?
670 */
671 if (!pDir->fDataUnread)
672 {
673 rc = rtDirNtFetchMore(pDir);
674 if (RT_FAILURE(rc))
675 return rc;
676 }
677
678 /*
679 * Convert the filename to UTF-8.
680 */
681 rc = rtDirNtConvertCurName(pDir);
682 if (RT_FAILURE(rc))
683 return rc;
684
685 /*
686 * Check if we've got enough space to return the data.
687 */
688 const char *pszName = pDir->pszName;
689 const size_t cchName = pDir->cchName;
690 const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRY, szName[1]) + cchName;
691 if (pcbDirEntry)
692 *pcbDirEntry = cbRequired;
693 if (cbRequired > cbDirEntry)
694 return VERR_BUFFER_OVERFLOW;
695
696 /*
697 * Setup the returned data.
698 */
699 pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName);
700 memcpy(pDirEntry->szName, pszName, cchName + 1);
701
702 pDirEntry->INodeId = pDir->enmInfoClass == FileIdBothDirectoryInformation
703 ? pDir->uCurData.pBothId->FileId.QuadPart : 0;
704
705#ifdef IPRT_WITH_NT_PATH_PASSTHRU
706 if (pDir->enmInfoClass != FileMaximumInformation)
707#endif
708 {
709 switch ( pDir->uCurData.pBoth->FileAttributes
710 & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
711 {
712 default:
713 AssertFailed();
714 case 0:
715 pDirEntry->enmType = RTDIRENTRYTYPE_FILE;
716 break;
717
718 case FILE_ATTRIBUTE_DIRECTORY:
719 pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY;
720 break;
721
722 case FILE_ATTRIBUTE_REPARSE_POINT:
723 case FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY:
724 /* EaSize is here reused for returning the repharse tag value. */
725 if (pDir->uCurData.pBoth->EaSize == IO_REPARSE_TAG_SYMLINK)
726 pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK;
727 break;
728 }
729 }
730#ifdef IPRT_WITH_NT_PATH_PASSTHRU
731 else
732 {
733 pDirEntry->enmType = RTDIRENTRYTYPE_UNKNOWN;
734 if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
735 RT_STR_TUPLE("Directory")))
736 pDirEntry->enmType = RTDIRENTRYTYPE_DIRECTORY;
737 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
738 RT_STR_TUPLE("SymbolicLink")))
739 pDirEntry->enmType = RTDIRENTRYTYPE_SYMLINK;
740 }
741#endif
742
743 return rtDirNtAdvanceBuffer(pDir);
744}
745
746
747RTDECL(int) RTDirReadEx(RTDIR hDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
748 RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags)
749{
750 PRTDIRINTERNAL pDir = hDir;
751 int rc;
752
753 /*
754 * Validate input.
755 */
756 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
757 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
758 AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);
759
760 AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
761 VERR_INVALID_PARAMETER);
762 AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
763
764 size_t cbDirEntry = sizeof(*pDirEntry);
765 if (pcbDirEntry)
766 {
767 cbDirEntry = *pcbDirEntry;
768 AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]),
769 ("Invalid *pcbDirEntry=%zu (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])),
770 VERR_INVALID_PARAMETER);
771 }
772
773 /*
774 * Fetch data?
775 */
776 if (!pDir->fDataUnread)
777 {
778 rc = rtDirNtFetchMore(pDir);
779 if (RT_FAILURE(rc))
780 return rc;
781 }
782
783 /*
784 * Convert the filename to UTF-8.
785 */
786 rc = rtDirNtConvertCurName(pDir);
787 if (RT_FAILURE(rc))
788 return rc;
789
790 /*
791 * Check if we've got enough space to return the data.
792 */
793 const char *pszName = pDir->pszName;
794 const size_t cchName = pDir->cchName;
795 const size_t cbRequired = RT_UOFFSETOF(RTDIRENTRYEX, szName[1]) + cchName;
796 if (pcbDirEntry)
797 *pcbDirEntry = cbRequired;
798 if (cbRequired > cbDirEntry)
799 return VERR_BUFFER_OVERFLOW;
800
801 /*
802 * Setup the returned data.
803 */
804 PFILE_BOTH_DIR_INFORMATION pBoth = pDir->uCurData.pBoth;
805
806 pDirEntry->cbName = (uint16_t)cchName; Assert(pDirEntry->cbName == cchName);
807 memcpy(pDirEntry->szName, pszName, cchName + 1);
808 memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName));
809#ifdef IPRT_WITH_NT_PATH_PASSTHRU
810 if (pDir->enmInfoClass != FileMaximumInformation)
811#endif
812 {
813 uint8_t cbShort = pBoth->ShortNameLength;
814 if (cbShort > 0)
815 {
816 AssertStmt(cbShort < sizeof(pDirEntry->wszShortName), cbShort = sizeof(pDirEntry->wszShortName) - 2);
817 memcpy(pDirEntry->wszShortName, pBoth->ShortName, cbShort);
818 pDirEntry->cwcShortName = cbShort / 2;
819 }
820 else
821 pDirEntry->cwcShortName = 0;
822
823 pDirEntry->Info.cbObject = pBoth->EndOfFile.QuadPart;
824 pDirEntry->Info.cbAllocated = pBoth->AllocationSize.QuadPart;
825
826 Assert(sizeof(uint64_t) == sizeof(pBoth->CreationTime));
827 RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, pBoth->CreationTime.QuadPart);
828 RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, pBoth->LastAccessTime.QuadPart);
829 RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, pBoth->LastWriteTime.QuadPart);
830 RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, pBoth->ChangeTime.QuadPart);
831
832 pDirEntry->Info.Attr.fMode = rtFsModeFromDos((pBoth->FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
833 pszName, cchName, pBoth->EaSize, 0);
834 }
835#ifdef IPRT_WITH_NT_PATH_PASSTHRU
836 else
837 {
838 pDirEntry->cwcShortName = 0;
839 pDirEntry->Info.cbObject = 0;
840 pDirEntry->Info.cbAllocated = 0;
841 RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, 0);
842 RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, 0);
843 RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, 0);
844 RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, 0);
845
846 if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
847 RT_STR_TUPLE("Directory")))
848 pDirEntry->Info.Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777;
849 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
850 RT_STR_TUPLE("SymbolicLink")))
851 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777;
852 else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
853 RT_STR_TUPLE("Device")))
854 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666;
855 else
856 pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666;
857 }
858#endif
859
860 /*
861 * Requested attributes (we cannot provide anything actually).
862 */
863 switch (enmAdditionalAttribs)
864 {
865 case RTFSOBJATTRADD_EASIZE:
866 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
867#ifdef IPRT_WITH_NT_PATH_PASSTHRU
868 if (pDir->enmInfoClass == FileMaximumInformation)
869 pDirEntry->Info.Attr.u.EASize.cb = 0;
870 else
871#endif
872 pDirEntry->Info.Attr.u.EASize.cb = pBoth->EaSize;
873 break;
874
875 case RTFSOBJATTRADD_UNIX:
876 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
877 pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID;
878 pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID;
879 pDirEntry->Info.Attr.u.Unix.cHardlinks = 1;
880 pDirEntry->Info.Attr.u.Unix.INodeIdDevice = pDir->uDirDev;
881 pDirEntry->Info.Attr.u.Unix.INodeId = 0;
882 if ( pDir->enmInfoClass == FileIdBothDirectoryInformation
883 && pDir->uCurData.pBothId->FileId.QuadPart != UINT64_MAX)
884 pDirEntry->Info.Attr.u.Unix.INodeId = pDir->uCurData.pBothId->FileId.QuadPart;
885 pDirEntry->Info.Attr.u.Unix.fFlags = 0;
886 pDirEntry->Info.Attr.u.Unix.GenerationId = 0;
887 pDirEntry->Info.Attr.u.Unix.Device = 0;
888 break;
889
890 case RTFSOBJATTRADD_NOTHING:
891 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING;
892 break;
893
894 case RTFSOBJATTRADD_UNIX_OWNER:
895 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
896 pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID;
897 pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */
898 break;
899
900 case RTFSOBJATTRADD_UNIX_GROUP:
901 pDirEntry->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
902 pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID;
903 pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0';
904 break;
905
906 default:
907 AssertMsgFailed(("Impossible!\n"));
908 return VERR_INTERNAL_ERROR;
909 }
910
911 /*
912 * Follow links if requested.
913 */
914 if ( (fFlags & RTPATH_F_FOLLOW_LINK)
915 && RTFS_IS_SYMLINK(fFlags))
916 {
917 /** @todo Symlinks: Find[First|Next]FileW will return info about
918 the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */
919 }
920
921 /*
922 * Finally advance the buffer.
923 */
924 return rtDirNtAdvanceBuffer(pDir);
925}
926
927
928RTDECL(int) RTDirRewind(RTDIR hDir)
929{
930 /*
931 * Validate and digest input.
932 */
933 PRTDIRINTERNAL pThis = hDir;
934 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
935 AssertReturn(pThis->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
936
937 /*
938 * The work is done on the next call to rtDirNtFetchMore.
939 */
940 pThis->fRestartScan = true;
941 pThis->fDataUnread = false;
942
943 return VINF_SUCCESS;
944}
945
946
947RTR3DECL(int) RTDirQueryInfo(RTDIR hDir, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs)
948{
949 PRTDIRINTERNAL pDir = hDir;
950 AssertPtrReturn(pDir, VERR_INVALID_POINTER);
951 AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
952 AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
953 VERR_INVALID_PARAMETER);
954
955 if (pDir->enmInfoClass == FileMaximumInformation)
956 {
957 /*
958 * Directory object (see similar code above and rtPathNtQueryInfoInDirectoryObject).
959 */
960 pObjInfo->cbObject = 0;
961 pObjInfo->cbAllocated = 0;
962 RTTimeSpecSetNtTime(&pObjInfo->BirthTime, 0);
963 RTTimeSpecSetNtTime(&pObjInfo->AccessTime, 0);
964 RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, 0);
965 RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, 0);
966 pObjInfo->Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777;
967 pObjInfo->Attr.enmAdditional = enmAdditionalAttribs;
968 switch (enmAdditionalAttribs)
969 {
970 case RTFSOBJATTRADD_NOTHING:
971 case RTFSOBJATTRADD_UNIX:
972 pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
973 pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
974 pObjInfo->Attr.u.Unix.cHardlinks = 1;
975 pObjInfo->Attr.u.Unix.INodeIdDevice = pDir->uDirDev;
976 pObjInfo->Attr.u.Unix.INodeId = 0;
977 pObjInfo->Attr.u.Unix.fFlags = 0;
978 pObjInfo->Attr.u.Unix.GenerationId = 0;
979 pObjInfo->Attr.u.Unix.Device = 0;
980 break;
981
982 case RTFSOBJATTRADD_EASIZE:
983 pObjInfo->Attr.u.EASize.cb = 0;
984 break;
985
986 case RTFSOBJATTRADD_UNIX_OWNER:
987 pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
988 pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
989 pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; /** @todo return something sensible here. */
990 break;
991
992 case RTFSOBJATTRADD_UNIX_GROUP:
993 pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
994 pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
995 pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
996 break;
997
998 default:
999 AssertMsgFailed(("Impossible!\n"));
1000 return VERR_INTERNAL_ERROR_2;
1001 }
1002 return VINF_SUCCESS;
1003 }
1004
1005 /*
1006 * Regular directory file.
1007 */
1008 uint8_t abBuf[_2K];
1009 return rtPathNtQueryInfoFromHandle(pDir->hDir, abBuf, sizeof(abBuf), pObjInfo, enmAdditionalAttribs, "", 0);
1010}
1011
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use