ZwQueryDirectoryFile API question enumerating files in a directory or in a volumes

Hello Guys,

Another question from a newbie here about the driver I created to enumerate files and folders inside the directory or in a volume.

Recently, I encountered problems querying list of files’ information in a directory. Here’s the code I created for this routine:

PFILE_BOTH_DIR_INFORMATION DirInfo;
OBJECT_ATTRIBUTES FileObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING DirectoryName;

DirInfo = ExAllocatePoolWithTag(
NonPagedPool,
sizeof(FILE_BOTH_DIR_INFORMATION),
TAG_GENERAL
);

if( DirInfo == NULL ) {
KdPrint( (“%s: Can’t Allocate Buffer.\n”, NAMEBASE) );
return (STATUS_INSUFFICIENT_RESOURCES);
}

RtlInitUnicodeString( &DirectoryName, L"\??\C:\TestDir" );

InitializeObjectAttributes(
&FileObjectAttributes,
&DirectoryName, //DirectoryName contains 1 file only
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);

Status = ZwCreateFile(
&DirectoryHandle,
FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_TRAVERSE,
&FileObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE | FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE
NULL,
0
);

if( !NT_SUCCESS( Status ) ) {
return Status;
} //endif

/*
NTSTATUS
ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
*/
Status = ZwQueryDirectoryFile(
DirectoryHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
(PVOID) DirInfo,
sizeof( FILE_BOTH_DIR_INFORMATION ),
FileBothDirectoryInformation,
TRUE,
NULL,
TRUE
);

if( !NT_SUCCESS( Status ) ) {
//Error Message
} //endif

ZwClose( DirectoryHandle );

Now, the problem I encountered is that the FileName and the FileNameLength it returns is not correct. The actual filename contained in the current directory is TestFile.txt but FileName field in FILE_BOTH_DIR_INFORMATION structure that is being returned by ZwQueryDirectoryFile is just “.” and the FileNameLength field is 2. The FileName field should have returned “T” instead of “.” and the FileNameLength should not equal to 2. The current code is designed only to return a single entry from the directory. Any thoughts on what I did wrong on the code above?

And also a follow-up question, is ZwQueryDirectoryFile can enumerate files on Drives like “??\C:” string as a directory name?

Thanks,

Xyber

> in the current directory is TestFile.txt but FileName field in

FILE_BOTH_DIR_INFORMATION structure that is being returned by
ZwQueryDirectoryFile is just “.” and the FileNameLength field is 2.
… which looks ok to me: you are going to get “.”, then “…”,
then your “TestFile.txt”.
Don’t ask not just 1 file, make a loop.

-----Original Message-----
From: xxxxx@lists.osr.com [mailto:bounce-323746-
xxxxx@lists.osr.com] On Behalf Of xxxxx@gmail.com
Sent: Friday, May 09, 2008 4:08 AM
To: Windows System Software Devs Interest List
Subject: [ntdev] ZwQueryDirectoryFile API question enumerating files in
a directory or in a volumes

Hello Guys,

Another question from a newbie here about the driver I created to
enumerate files and folders inside the directory or in a volume.

Recently, I encountered problems querying list of files’ information in
a directory. Here’s the code I created for this routine:

PFILE_BOTH_DIR_INFORMATION DirInfo;
OBJECT_ATTRIBUTES FileObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING DirectoryName;

DirInfo = ExAllocatePoolWithTag(
NonPagedPool,

sizeof(FILE_BOTH_DIR_INFORMATION),
TAG_GENERAL
);

if( DirInfo == NULL ) {
KdPrint( (“%s: Can’t Allocate Buffer.\n”, NAMEBASE) );
return (STATUS_INSUFFICIENT_RESOURCES);
}

RtlInitUnicodeString( &DirectoryName, L"\??\C:\TestDir" );

InitializeObjectAttributes(
&FileObjectAttributes,
&DirectoryName,
//DirectoryName contains 1 file only
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);

Status = ZwCreateFile(
&DirectoryHandle,
FILE_LIST_DIRECTORY | SYNCHRONIZE |
FILE_TRAVERSE,
&FileObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE | FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT |
FILE_DIRECTORY_FILE
NULL,
0
);

if( !NT_SUCCESS( Status ) ) {
return Status;
} //endif

/*
NTSTATUS
ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
*/
Status = ZwQueryDirectoryFile(
DirectoryHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
(PVOID) DirInfo,
sizeof( FILE_BOTH_DIR_INFORMATION
),
FileBothDirectoryInformation,
TRUE,
NULL,
TRUE
);

if( !NT_SUCCESS( Status ) ) {
//Error Message
} //endif

ZwClose( DirectoryHandle );

Now, the problem I encountered is that the FileName and the
FileNameLength it returns is not correct. The actual filename contained
in the current directory is TestFile.txt but FileName field in
FILE_BOTH_DIR_INFORMATION structure that is being returned by
ZwQueryDirectoryFile is just “.” and the FileNameLength field is 2. The
FileName field should have returned “T” instead of “.” and the
FileNameLength should not equal to 2. The current code is designed only
to return a single entry from the directory. Any thoughts on what I did
wrong on the code above?

And also a follow-up question, is ZwQueryDirectoryFile can enumerate
files on Drives like “??\C:” string as a directory name?

Thanks,

Xyber


NTDEV is sponsored by OSR

For our schedule of WDF, WDM, debugging and other seminars visit:
http://www.osr.com/seminars

To unsubscribe, visit the List Server section of OSR Online at
http://www.osronline.com/page.cfm?name=ListServer

There are a few issues you have to care.

First: you pass ReturnSingleEntry equal to TRUE to ZwQueryDirectoryFile
which is actually not what exactly suits you, because only one instance of
FILE_BOTH_DIR_INFORMATION is returned, and it corresponds only to one file.
If you need to handle multiple files you have to pass FALSE as
ReturnSingleEntry. You also have to iteration several times calling
ZwQueryDirectoryFile untill you recieve STATUS_NO_MORE_FILES.

Second: you have to take care about the size of structure you pass into
ZwQueryDirectoryFile. You have to call ZwQueryDirectoryFile and check
against STATUS_BUFFER_OVERFLOW error. Once you get, you have to re-allocate
your buffer and try again.

I just changed your code a little, it should work. You can copy it below, or
copy a well-formatted C file from the following link (I suggest to download
C file because the code pasted here will become unformatted):
http://msmvps.com/blogs/v_scherbina/pages/samples.aspx

NTSTATUS EnumFilesInDir()
{
HANDLE hFile = NULL;
UNICODE_STRING szFileName = {0};
OBJECT_ATTRIBUTES Oa = {0};
NTSTATUS ntStatus = 0;
IO_STATUS_BLOCK Iosb = {0};
UINT uSize = sizeof(FILE_BOTH_DIR_INFORMATION);
FILE_BOTH_DIR_INFORMATION *pfbInfo = NULL;
BOOLEAN bIsStarted = TRUE;

PAGED_CODE();

RtlInitUnicodeString(&szFileName, L"\??\C:\"); /// let it show files in
C drive
InitializeObjectAttributes(&Oa, &szFileName, OBJ_CASE_INSENSITIVE |
OBJ_KERNEL_HANDLE, NULL, NULL);

ntStatus = ZwCreateFile(&hFile, GENERIC_READ | SYNCHRONIZE, &Oa, &Iosb, 0,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

if (!NT_SUCCESS(ntStatus))
{
return ntStatus;
}

pfbInfo = ExAllocatePoolWithTag(PagedPool, uSize, ‘0000’);

if (pfbInfo == NULL)
{
ZwClose(hFile);

return STATUS_NO_MEMORY;
}

while (TRUE)
{
lbl_retry:
RtlZeroMemory(pfbInfo, uSize);
ntStatus = ZwQueryDirectoryFile(hFile, 0, NULL, NULL, &Iosb, pfbInfo,
uSize, FileBothDirectoryInformation, FALSE, NULL, bIsStarted);

if (STATUS_BUFFER_OVERFLOW == ntStatus)
{
ExFreePoolWithTag(pfbInfo, ‘000’);

uSize = uSize * 2;
pfbInfo = ExAllocatePoolWithTag(PagedPool, uSize, ‘0000’);

if (pfbInfo == NULL)
{
ZwClose(hFile);

return STATUS_NO_MEMORY;
}

goto lbl_retry;
}
else if (STATUS_NO_MORE_FILES == ntStatus)
{
ExFreePoolWithTag(pfbInfo, ‘000’);
ZwClose(hFile);

return STATUS_SUCCESS;
}
else if (STATUS_SUCCESS != ntStatus)
{
ExFreePoolWithTag(pfbInfo, ‘000’);
ZwClose(hFile);

return ntStatus;
}

if (bIsStarted)
{
bIsStarted = FALSE;
}

while (TRUE)
{
WCHAR * szWellFormedFileName = ExAllocatePoolWithTag(PagedPool,
(pfbInfo->FileNameLength + sizeof(WCHAR)), ‘0001’);

if (szWellFormedFileName)
{
RtlZeroMemory(szWellFormedFileName, (pfbInfo->FileNameLength +
sizeof(WCHAR)));
RtlCopyMemory(szWellFormedFileName, pfbInfo->FileName,
pfbInfo->FileNameLength);

KdPrint((“File name is: %S\n”, szWellFormedFileName));
ExFreePoolWithTag(szWellFormedFileName, ‘000’);
}

if (pfbInfo->NextEntryOffset == 0)
{
break;
}

pfbInfo += pfbInfo->NextEntryOffset;
}
}

ZwClose(hFile);
ExFreePoolWithTag(pfbInfo, ‘000’);

return ntStatus;
}


V.
This posting is provided “AS IS” with no warranties, and confers no
rights.
wrote in message news:xxxxx@ntdev…
> Hello Guys,
>
> Another question from a newbie here about the driver I created to
> enumerate files and folders inside the directory or in a volume.
>
> Recently, I encountered problems querying list of files’ information in a
> directory. Here’s the code I created for this routine:
>
>
>
> PFILE_BOTH_DIR_INFORMATION DirInfo;
> OBJECT_ATTRIBUTES FileObjectAttributes;
> IO_STATUS_BLOCK IoStatusBlock;
> UNICODE_STRING DirectoryName;
>
> DirInfo = ExAllocatePoolWithTag(
> NonPagedPool,
> sizeof(FILE_BOTH_DIR_INFORMATION),
> TAG_GENERAL
> );
>
>
> if( DirInfo == NULL ) {
> KdPrint( (“%s: Can’t Allocate Buffer.\n”, NAMEBASE) );
> return (STATUS_INSUFFICIENT_RESOURCES);
> }
>
> RtlInitUnicodeString( &DirectoryName, L"\??\C:\TestDir" );
>
> InitializeObjectAttributes(
> &FileObjectAttributes,
> &DirectoryName, //DirectoryName contains 1 file only
> OBJ_CASE_INSENSITIVE,
> NULL,
> NULL
> );
>
> Status = ZwCreateFile(
> &DirectoryHandle,
> FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_TRAVERSE,
> &FileObjectAttributes,
> &IoStatusBlock,
> NULL,
> FILE_ATTRIBUTE_NORMAL,
> FILE_SHARE_WRITE | FILE_SHARE_READ,
> FILE_OPEN,
> FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE
> NULL,
> 0
> );
>
>
> if( !NT_SUCCESS( Status ) ) {
> return Status;
> } //endif
>
> /*
> NTSTATUS
> ZwQueryDirectoryFile(
> IN HANDLE FileHandle,
> IN HANDLE Event OPTIONAL,
> IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
> IN PVOID ApcContext OPTIONAL,
> OUT PIO_STATUS_BLOCK IoStatusBlock,
> OUT PVOID FileInformation,
> IN ULONG Length,
> IN FILE_INFORMATION_CLASS FileInformationClass,
> IN BOOLEAN ReturnSingleEntry,
> IN PUNICODE_STRING FileName OPTIONAL,
> IN BOOLEAN RestartScan
> );
> */
> Status = ZwQueryDirectoryFile(
> DirectoryHandle,
> NULL,
> NULL,
> NULL,
> &IoStatusBlock,
> (PVOID) DirInfo,
> sizeof( FILE_BOTH_DIR_INFORMATION ),
> FileBothDirectoryInformation,
> TRUE,
> NULL,
> TRUE
> );
>
> if( !NT_SUCCESS( Status ) ) {
> //Error Message
> } //endif
>
> ZwClose( DirectoryHandle );
>
> Now, the problem I encountered is that the FileName and the FileNameLength
> it returns is not correct. The actual filename contained in the current
> directory is TestFile.txt but FileName field in FILE_BOTH_DIR_INFORMATION
> structure that is being returned by ZwQueryDirectoryFile is just “.” and
> the FileNameLength field is 2. The FileName field should have returned “T”
> instead of “.” and the FileNameLength should not equal to 2. The current
> code is designed only to return a single entry from the directory. Any
> thoughts on what I did wrong on the code above?
>
> And also a follow-up question, is ZwQueryDirectoryFile can enumerate files
> on Drives like “??\C:” string as a directory name?
>
> Thanks,
>
> Xyber
>

Hi Volodymyr,

Thanks for the code above and it’s really a big help for my implementation.
Hoping to see more samples from you soon.
Keep it up. :slight_smile: