/** * Advances the buffer pointer. * * @param pThis The directory instance data. */ static int rtDirNtAdvanceBuffer(PRTDIR pThis) { int rc; #ifdef IPRT_WITH_NT_PATH_PASSTHRU if (pThis->enmInfoClass == FileMaximumInformation) { pThis->uCurData.pObjDir++; pThis->fDataUnread = pThis->uCurData.pObjDir->Name.Length != 0; return VINF_SUCCESS; } #endif pThis->fDataUnread = false; uint32_t const offNext = pThis->uCurData.pBoth->NextEntryOffset; if (offNext == 0) return VINF_SUCCESS; #ifdef RTDIR_NT_STRICT /* Make sure the next-record offset is beyond the current record. */ size_t cbRec; if (pThis->enmInfoClass == FileIdBothDirectoryInformation) cbRec = RT_UOFFSETOF(FILE_ID_BOTH_DIR_INFORMATION, FileName); else cbRec = RT_UOFFSETOF(FILE_BOTH_DIR_INFORMATION, FileName); cbRec += pThis->uCurData.pBoth->FileNameLength; AssertReturn(offNext >= cbRec, VERR_IO_GEN_FAILURE); #endif pThis->uCurData.u += offNext; rc = rtDirNtCheckRecord(pThis); pThis->fDataUnread = RT_SUCCESS(rc); return rc; }
/** * Fetches more data from the file system. * * @returns IPRT status code * @param pThis The directory instance data. */ static int rtDirNtFetchMore(PRTDIR pThis) { Assert(!pThis->fDataUnread); /* * Allocate the buffer the first time around. * We do this in lazy fashion as some users of RTDirOpen will not actually * list any files, just open it for various reasons. */ bool fFirst = false; if (!pThis->pabBuffer) { fFirst = false; pThis->cbBufferAlloc = _256K; pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); if (!pThis->pabBuffer) { do { pThis->cbBufferAlloc /= 4; pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K); if (!pThis->pabBuffer) return VERR_NO_MEMORY; } } /* * Read more. */ NTSTATUS rcNt; IO_STATUS_BLOCK Ios = MY_IO_STATUS_BLOCK_INITIALIZER; if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0) { #ifdef IPRT_WITH_NT_PATH_PASSTHRU if (pThis->enmInfoClass == FileMaximumInformation) { Ios.Information = 0; Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir, pThis->pabBuffer, pThis->cbBufferAlloc, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, FALSE /*RestartScan*/, &pThis->uObjDirCtx, (PULONG)&Ios.Information); } else #endif rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, pThis->cbBufferAlloc, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, FALSE /*RestartScan */); } else { /* * The first time around we have figure which info class we can use. * We prefer one which gives us file IDs, but we'll settle for less. */ pThis->enmInfoClass = FileIdBothDirectoryInformation; rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, pThis->cbBufferAlloc, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, FALSE /*RestartScan */); if (!NT_SUCCESS(rcNt)) { pThis->enmInfoClass = FileBothDirectoryInformation; rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, pThis->cbBufferAlloc, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, FALSE /*RestartScan */); } } if (!NT_SUCCESS(rcNt)) { if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES) return VERR_NO_MORE_FILES; return RTErrConvertFromNtStatus(rcNt); } Assert(Ios.Information > sizeof(*pThis->uCurData.pBoth)); /* * Set up the data members. */ pThis->uCurData.u = (uintptr_t)pThis->pabBuffer; pThis->cbBuffer = Ios.Information; int rc = rtDirNtCheckRecord(pThis); pThis->fDataUnread = RT_SUCCESS(rc); return rc; }
/** * Fetches more data from the file system. * * @returns IPRT status code * @param pThis The directory instance data. */ static int rtDirNtFetchMore(PRTDIRINTERNAL pThis) { Assert(!pThis->fDataUnread); /* * Allocate the buffer the first time around. * We do this in lazy fashion as some users of RTDirOpen will not actually * list any files, just open it for various reasons. * * We also reduce the buffer size for networked devices as the windows 7-8.1, * server 2012, ++ CIFS servers or/and IFSes screws up buffers larger than 64KB. * There is an alternative hack below, btw. We'll leave both in for now. */ bool fFirst = false; if (!pThis->pabBuffer) { pThis->cbBufferAlloc = _256K; if (true) /** @todo skip for known local devices, like the boot device? */ { IO_STATUS_BLOCK Ios2 = RTNT_IO_STATUS_BLOCK_INITIALIZER; FILE_FS_DEVICE_INFORMATION Info = { 0, 0 }; NTSTATUS rcNt2 = NtQueryVolumeInformationFile(pThis->hDir, &Ios2, &Info, sizeof(Info), FileFsDeviceInformation); if ( !NT_SUCCESS(rcNt2) || (Info.Characteristics & FILE_REMOTE_DEVICE) || Info.DeviceType == FILE_DEVICE_NETWORK || Info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM || Info.DeviceType == FILE_DEVICE_NETWORK_REDIRECTOR || Info.DeviceType == FILE_DEVICE_SMB) pThis->cbBufferAlloc = _64K; } fFirst = false; pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); if (!pThis->pabBuffer) { do { pThis->cbBufferAlloc /= 4; pThis->pabBuffer = (uint8_t *)RTMemAlloc(pThis->cbBufferAlloc); } while (pThis->pabBuffer == NULL && pThis->cbBufferAlloc > _4K); if (!pThis->pabBuffer) return VERR_NO_MEMORY; } /* * Also try determining the device number. */ PFILE_FS_VOLUME_INFORMATION pVolInfo = (PFILE_FS_VOLUME_INFORMATION)pThis->pabBuffer; pVolInfo->VolumeSerialNumber = 0; IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; NTSTATUS rcNt = NtQueryVolumeInformationFile(pThis->hDir, &Ios, pVolInfo, RT_MIN(_2K, pThis->cbBufferAlloc), FileFsVolumeInformation); if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) pThis->uDirDev = pVolInfo->VolumeSerialNumber; else pThis->uDirDev = 0; AssertCompile(sizeof(pThis->uDirDev) == sizeof(pVolInfo->VolumeSerialNumber)); /** @todo Grow RTDEV to 64-bit and add low dword of VolumeCreationTime to the top of uDirDev. */ } /* * Read more. */ NTSTATUS rcNt; IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; if (pThis->enmInfoClass != (FILE_INFORMATION_CLASS)0) { #ifdef IPRT_WITH_NT_PATH_PASSTHRU if (pThis->enmInfoClass == FileMaximumInformation) { Ios.Information = 0; Ios.Status = rcNt = NtQueryDirectoryObject(pThis->hDir, pThis->pabBuffer, pThis->cbBufferAlloc, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->fRestartScan, &pThis->uObjDirCtx, (PULONG)&Ios.Information); } else #endif rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, pThis->cbBufferAlloc, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, pThis->fRestartScan); } else { /* * The first time around we have to figure which info class we can use * as well as the right buffer size. We prefer an info class which * gives us file IDs (Vista+ IIRC) and we prefer large buffers (for long * ReFS file names and such), but we'll settle for whatever works... * * The windows 7 thru 8.1 CIFS servers have been observed to have * trouble with large buffers, but weirdly only when listing large * directories. Seems 0x10000 is the max. (Samba does not exhibit * these problems, of course.) * * This complicates things. The buffer size issues causes an * STATUS_INVALID_PARAMETER error. Now, you would expect the lack of * FileIdBothDirectoryInformation support to return * STATUS_INVALID_INFO_CLASS, but I'm not entirely sure if we can 100% * depend on third IFSs to get that right. Nor, am I entirely confident * that we can depend on them to check the class before the buffer size. * * Thus the mess. */ if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */) pThis->enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */ else pThis->enmInfoClass = FileBothDirectoryInformation; rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, pThis->cbBufferAlloc, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, pThis->fRestartScan); if (NT_SUCCESS(rcNt)) { /* likely */ } else { bool fRestartScan = pThis->fRestartScan; for (unsigned iRetry = 0; iRetry < 2; iRetry++) { if ( rcNt == STATUS_INVALID_INFO_CLASS || rcNt == STATUS_INVALID_PARAMETER_8 || iRetry != 0) pThis->enmInfoClass = FileBothDirectoryInformation; uint32_t cbBuffer = pThis->cbBufferAlloc; if ( rcNt == STATUS_INVALID_PARAMETER || rcNt == STATUS_INVALID_PARAMETER_7 || rcNt == STATUS_INVALID_NETWORK_RESPONSE || iRetry != 0) { cbBuffer = RT_MIN(cbBuffer / 2, _64K); fRestartScan = true; } for (;;) { rcNt = NtQueryDirectoryFile(pThis->hDir, NULL /* Event */, NULL /* ApcRoutine */, NULL /* ApcContext */, &Ios, pThis->pabBuffer, cbBuffer, pThis->enmInfoClass, RTDIR_NT_SINGLE_RECORD /*ReturnSingleEntry */, pThis->pNtFilterStr, fRestartScan); if ( NT_SUCCESS(rcNt) || cbBuffer == pThis->cbBufferAlloc || cbBuffer <= sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260) break; /* Reduce the buffer size agressivly and try again. We fall back to FindFirstFile values for the final lap. This means we'll do 4 rounds with the current initial buffer size (64KB, 8KB, 1KB, 0x278/0x268). */ cbBuffer /= 8; if (cbBuffer < 1024) cbBuffer = pThis->enmInfoClass == FileIdBothDirectoryInformation ? sizeof(*pThis->uCurData.pBothId) + sizeof(WCHAR) * 260 : sizeof(*pThis->uCurData.pBoth) + sizeof(WCHAR) * 260; } if (NT_SUCCESS(rcNt)) { pThis->cbBufferAlloc = cbBuffer; break; } } } } if (!NT_SUCCESS(rcNt)) { /* Note! VBoxSVR and CIFS file systems both ends up with STATUS_NO_SUCH_FILE here instead of STATUS_NO_MORE_FILES. */ if (rcNt == STATUS_NO_MORE_FILES || rcNt == STATUS_NO_MORE_ENTRIES || rcNt == STATUS_NO_SUCH_FILE) return VERR_NO_MORE_FILES; return RTErrConvertFromNtStatus(rcNt); } pThis->fRestartScan = false; AssertMsg( Ios.Information > (pThis->enmInfoClass == FileMaximumInformation ? sizeof(*pThis->uCurData.pObjDir) : sizeof(*pThis->uCurData.pBoth)), ("Ios.Information=%#x\n", Ios.Information)); /* * Set up the data members. */ pThis->uCurData.u = (uintptr_t)pThis->pabBuffer; pThis->cbBuffer = Ios.Information; int rc = rtDirNtCheckRecord(pThis); pThis->fDataUnread = RT_SUCCESS(rc); return rc; }