2730 lines
85 KiB
C
2730 lines
85 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
info.c
|
||
|
||
Abstract:
|
||
|
||
This module contains various routines for obtaining information such as
|
||
times, dates, etc. that is to be returned by SMBs and for converting
|
||
information that is given by request SMBs.
|
||
|
||
Author:
|
||
|
||
David Treadwell (davidtr) 30-Nov-1989
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "precomp.h"
|
||
#include "info.tmh"
|
||
#pragma hdrstop
|
||
|
||
#define BugCheckFileId SRV_FILE_INFO
|
||
|
||
NTSTATUS
|
||
BruteForceRewind(
|
||
IN HANDLE DirectoryHandle,
|
||
IN PVOID Buffer,
|
||
IN ULONG BufferLength,
|
||
IN PUNICODE_STRING FileName,
|
||
IN FILE_INFORMATION_CLASS FileInformationClass,
|
||
IN OUT PFILE_DIRECTORY_INFORMATION *CurrentEntry
|
||
);
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text( PAGE, SrvCloseQueryDirectory )
|
||
#pragma alloc_text( PAGE, SrvQueryInformationFile )
|
||
#pragma alloc_text( PAGE, SrvQueryInformationFileAbbreviated )
|
||
#pragma alloc_text( PAGE, SrvQueryNtInformationFile )
|
||
#pragma alloc_text( PAGE, SrvQueryDirectoryFile )
|
||
#pragma alloc_text( PAGE, BruteForceRewind )
|
||
#pragma alloc_text( PAGE, SrvQueryEaFile )
|
||
#pragma alloc_text( PAGE, SrvTimeToDosTime )
|
||
#pragma alloc_text( PAGE, SrvDosTimeToTime )
|
||
#pragma alloc_text( PAGE, SrvGetOs2TimeZone )
|
||
#pragma alloc_text( PAGE, SrvQueryBasicAndStandardInformation )
|
||
#pragma alloc_text( PAGE, SrvQueryNetworkOpenInformation )
|
||
#endif
|
||
|
||
|
||
VOID
|
||
SrvCloseQueryDirectory (
|
||
IN PSRV_DIRECTORY_INFORMATION DirectoryInformation
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine cleans up after a directory search was aborted before
|
||
SrvQueryDirectoryFile is done. It closes the directory handle.
|
||
|
||
Arguments:
|
||
|
||
DirectoryInformation - pointer to the buffer that is being used for
|
||
SrvQueryDirectoryFile.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Close the directory handle.
|
||
//
|
||
|
||
if ( DirectoryInformation->DirectoryHandle != NULL ) {
|
||
SRVDBG_RELEASE_HANDLE( DirectoryInformation->DirectoryHandle, "DID", 8, DirectoryInformation );
|
||
SrvNtClose( DirectoryInformation->DirectoryHandle, TRUE );
|
||
}
|
||
|
||
DirectoryInformation->DirectoryHandle = NULL;
|
||
|
||
} // SrvCloseQueryDirectory
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SrvQueryInformationFile (
|
||
IN HANDLE FileHandle,
|
||
IN PFILE_OBJECT FileObject OPTIONAL,
|
||
OUT PSRV_FILE_INFORMATION SrvFileInformation,
|
||
IN SHARE_TYPE ShareType,
|
||
IN BOOLEAN QueryEaSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine makes calls to NtQueryInformationFile to get information
|
||
about a file opened by the server.
|
||
|
||
Arguments:
|
||
|
||
FileHandle - a handle of the file to get information about.
|
||
|
||
FileInformation - pointer to a structure in which to store the
|
||
information.
|
||
|
||
ShareType - The file type. It will be disk, comm, print, pipe
|
||
or (-1) for don't care.
|
||
|
||
QueryEaSize - Try if EA size info is requested.
|
||
|
||
Return Value:
|
||
|
||
A status indicating success or failure of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
SRV_NETWORK_OPEN_INFORMATION srvNetworkOpenInformation;
|
||
FILE_PIPE_LOCAL_INFORMATION pipeLocalInformation;
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Most query operations will fail on comm devices and print shares.
|
||
// If this is a disk file, etc. do the queries. If it is a comm
|
||
// device, fake it with defaults.
|
||
//
|
||
|
||
if ( ShareType != ShareTypePrint )
|
||
{
|
||
|
||
status = SrvQueryNetworkOpenInformation( FileHandle,
|
||
FileObject,
|
||
&srvNetworkOpenInformation,
|
||
QueryEaSize
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
" failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
} else {
|
||
//
|
||
// Use defaults for comm and print shares.
|
||
//
|
||
|
||
RtlZeroMemory( &srvNetworkOpenInformation, sizeof( srvNetworkOpenInformation ) );
|
||
}
|
||
|
||
if ( ShareType == ShareTypePipe ) {
|
||
|
||
FILE_PIPE_INFORMATION pipeInformation;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
USHORT pipeHandleState;
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeInformation,
|
||
sizeof(pipeInformation),
|
||
FilePipeInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeLocalInformation,
|
||
sizeof(pipeLocalInformation),
|
||
FilePipeLocalInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe local information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Fill in the handle state information in SMB format
|
||
//
|
||
|
||
pipeHandleState = (USHORT)pipeInformation.CompletionMode
|
||
<< PIPE_COMPLETION_MODE_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeEnd
|
||
<< PIPE_PIPE_END_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeType
|
||
<< PIPE_PIPE_TYPE_BITS;
|
||
pipeHandleState |= (USHORT)pipeInformation.ReadMode
|
||
<< PIPE_READ_MODE_BITS;
|
||
pipeHandleState |= (USHORT)(pipeLocalInformation.MaximumInstances &
|
||
0xFF)
|
||
<< PIPE_MAXIMUM_INSTANCES_BITS;
|
||
|
||
SrvFileInformation->HandleState = pipeHandleState;
|
||
} else {
|
||
|
||
SrvFileInformation->HandleState = 0;
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Set up creation time fields.
|
||
//
|
||
|
||
{
|
||
LARGE_INTEGER newTime;
|
||
|
||
ExSystemTimeToLocalTime(
|
||
&srvNetworkOpenInformation.LastWriteTime,
|
||
&newTime
|
||
);
|
||
|
||
|
||
//
|
||
// Make sure we round up to two seconds.
|
||
//
|
||
|
||
newTime.QuadPart += AlmostTwoSeconds;
|
||
|
||
if ( !RtlTimeToSecondsSince1970(
|
||
&newTime,
|
||
&SrvFileInformation->LastWriteTimeInSeconds
|
||
) ) {
|
||
|
||
SrvFileInformation->LastWriteTimeInSeconds = 0;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Mask off the low bit so we can be consistent with LastWriteTime.
|
||
// (We need to round up to 2 seconds)
|
||
//
|
||
|
||
SrvFileInformation->LastWriteTimeInSeconds &= ~1;
|
||
}
|
||
|
||
}
|
||
|
||
SrvTimeToDosTime(
|
||
&srvNetworkOpenInformation.LastWriteTime,
|
||
&SrvFileInformation->LastWriteDate,
|
||
&SrvFileInformation->LastWriteTime
|
||
);
|
||
|
||
if( srvNetworkOpenInformation.CreationTime.QuadPart == srvNetworkOpenInformation.LastWriteTime.QuadPart ) {
|
||
SrvFileInformation->CreationDate = SrvFileInformation->LastWriteDate;
|
||
SrvFileInformation->CreationTime = SrvFileInformation->LastWriteTime;
|
||
} else {
|
||
SrvTimeToDosTime(
|
||
&srvNetworkOpenInformation.CreationTime,
|
||
&SrvFileInformation->CreationDate,
|
||
&SrvFileInformation->CreationTime
|
||
);
|
||
}
|
||
|
||
if( srvNetworkOpenInformation.LastAccessTime.QuadPart == srvNetworkOpenInformation.LastWriteTime.QuadPart ) {
|
||
SrvFileInformation->LastAccessDate = SrvFileInformation->LastWriteDate;
|
||
SrvFileInformation->LastAccessTime = SrvFileInformation->LastWriteTime;
|
||
|
||
} else {
|
||
|
||
SrvTimeToDosTime(
|
||
&srvNetworkOpenInformation.LastAccessTime,
|
||
&SrvFileInformation->LastAccessDate,
|
||
&SrvFileInformation->LastAccessTime
|
||
);
|
||
}
|
||
|
||
//
|
||
// Set File Attributes field of structure.
|
||
//
|
||
|
||
SRV_NT_ATTRIBUTES_TO_SMB(
|
||
srvNetworkOpenInformation.FileAttributes,
|
||
srvNetworkOpenInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY,
|
||
&SrvFileInformation->Attributes
|
||
);
|
||
|
||
//
|
||
// Set up allocation and data sizes.
|
||
//
|
||
// *** Note the assumption that the high part of the 64-bit
|
||
// allocation and EOF size is zero. If it's not (i.e., the file
|
||
// is bigger than 4GB), then we're out of luck, because the SMB
|
||
// protocol can't express that.
|
||
//
|
||
|
||
SrvFileInformation->AllocationSize.QuadPart =
|
||
srvNetworkOpenInformation.AllocationSize.QuadPart;
|
||
|
||
SrvFileInformation->DataSize.QuadPart =
|
||
srvNetworkOpenInformation.EndOfFile.QuadPart;
|
||
|
||
|
||
//
|
||
// Set the file device type.
|
||
//
|
||
|
||
switch( ShareType ) {
|
||
|
||
case ShareTypeDisk:
|
||
|
||
SrvFileInformation->Type = FileTypeDisk;
|
||
break;
|
||
|
||
case ShareTypePipe:
|
||
|
||
if (pipeLocalInformation.NamedPipeType == FILE_PIPE_MESSAGE_TYPE) {
|
||
SrvFileInformation->Type = FileTypeMessageModePipe;
|
||
} else {
|
||
SrvFileInformation->Type = FileTypeByteModePipe;
|
||
}
|
||
break;
|
||
|
||
case ShareTypePrint:
|
||
|
||
SrvFileInformation->Type = FileTypePrinter;
|
||
break;
|
||
|
||
default:
|
||
|
||
SrvFileInformation->Type = FileTypeUnknown;
|
||
|
||
}
|
||
|
||
//
|
||
// If the caller wants to know the length of the file's extended
|
||
// attributes, obtain them now.
|
||
//
|
||
|
||
if ( QueryEaSize ) {
|
||
|
||
//
|
||
// If the file has no EAs, return an FEA size = 4 (that's what OS/2
|
||
// does--it accounts for the size of the cbList field of an
|
||
// FEALIST).
|
||
//
|
||
|
||
if ( srvNetworkOpenInformation.EaSize == 0 ) {
|
||
SrvFileInformation->EaSize = 4;
|
||
} else {
|
||
SrvFileInformation->EaSize = srvNetworkOpenInformation.EaSize;
|
||
}
|
||
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueryInformationFile
|
||
|
||
NTSTATUS
|
||
SrvQueryInformationFileAbbreviated(
|
||
IN HANDLE FileHandle,
|
||
IN PFILE_OBJECT FileObject OPTIONAL,
|
||
OUT PSRV_FILE_INFORMATION_ABBREVIATED SrvFileInformation,
|
||
IN BOOLEAN AdditionalInfo,
|
||
IN SHARE_TYPE ShareType
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine makes calls to NtQueryInformationFile to get information
|
||
about a file opened by the server.
|
||
|
||
Arguments:
|
||
|
||
FileHandle - a handle of the file to get information about.
|
||
|
||
FileInformation - pointer to a structure in which to store the
|
||
information.
|
||
|
||
ShareType - The file type. It will be disk, comm, print, pipe
|
||
or (-1) for don't care.
|
||
|
||
QueryEaSize - Try if EA size info is requested.
|
||
|
||
Return Value:
|
||
|
||
A status indicating success or failure of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
SRV_NETWORK_OPEN_INFORMATION srvNetworkOpenInformation;
|
||
NTSTATUS status;
|
||
LARGE_INTEGER newTime;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Most query operations will fail on comm devices and print shares.
|
||
// If this is a disk file, etc. do the queries. If it is a comm
|
||
// device, fake it with defaults.
|
||
//
|
||
|
||
if ( ShareType != ShareTypePrint ) {
|
||
|
||
status = SrvQueryNetworkOpenInformation(
|
||
FileHandle,
|
||
FileObject,
|
||
&srvNetworkOpenInformation,
|
||
FALSE
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
" failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Use defaults for comm and print shares.
|
||
//
|
||
|
||
RtlZeroMemory( &srvNetworkOpenInformation, sizeof( srvNetworkOpenInformation ) );
|
||
}
|
||
|
||
//
|
||
// Set up creation time fields.
|
||
//
|
||
ExSystemTimeToLocalTime(
|
||
&srvNetworkOpenInformation.LastWriteTime,
|
||
&newTime
|
||
);
|
||
|
||
//
|
||
// Make sure we round up to two seconds.
|
||
//
|
||
|
||
newTime.QuadPart += AlmostTwoSeconds;
|
||
|
||
if ( !RtlTimeToSecondsSince1970(
|
||
&newTime,
|
||
&SrvFileInformation->LastWriteTimeInSeconds
|
||
) ) {
|
||
|
||
SrvFileInformation->LastWriteTimeInSeconds = 0;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Mask off the low bit so we can be consistent with LastWriteTime.
|
||
// (We need to round up to 2 seconds)
|
||
//
|
||
|
||
SrvFileInformation->LastWriteTimeInSeconds &= ~1;
|
||
}
|
||
|
||
//
|
||
// Set File Attributes field of structure.
|
||
//
|
||
|
||
SRV_NT_ATTRIBUTES_TO_SMB(
|
||
srvNetworkOpenInformation.FileAttributes,
|
||
srvNetworkOpenInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY,
|
||
&SrvFileInformation->Attributes
|
||
);
|
||
|
||
SrvFileInformation->DataSize.QuadPart =
|
||
srvNetworkOpenInformation.EndOfFile.QuadPart;
|
||
|
||
//
|
||
// Set the file device type.
|
||
//
|
||
|
||
switch( ShareType ) {
|
||
|
||
case ShareTypeDisk: {
|
||
|
||
SrvFileInformation->Type = FileTypeDisk;
|
||
SrvFileInformation->HandleState = 0;
|
||
|
||
if( AdditionalInfo ) {
|
||
|
||
union {
|
||
FILE_EA_INFORMATION eaInformation;
|
||
FILE_STREAM_INFORMATION streamInformation;
|
||
FILE_ATTRIBUTE_TAG_INFORMATION tagInformation;
|
||
ULONG buffer[ (sizeof( FILE_STREAM_INFORMATION ) + 14) / sizeof(ULONG) ];
|
||
} u;
|
||
|
||
//
|
||
// Find out if this file has EAs
|
||
//
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.eaInformation,
|
||
sizeof( u.eaInformation ),
|
||
FileEaInformation
|
||
);
|
||
|
||
if( !NT_SUCCESS( status ) || u.eaInformation.EaSize == 0 ) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_EAS;
|
||
}
|
||
|
||
//
|
||
// Find out if this file has substreams.
|
||
//
|
||
RtlZeroMemory( &u, sizeof(u) );
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.streamInformation,
|
||
sizeof( u.streamInformation ),
|
||
FileStreamInformation
|
||
);
|
||
|
||
|
||
//
|
||
// If the filesystem does not support this call, then there are no substreams. Or
|
||
// If the filesystem supports the call but returned exactly no name or returned "::$DATA"
|
||
// then there are no substreams.
|
||
//
|
||
if( status == STATUS_INVALID_PARAMETER ||
|
||
status == STATUS_NOT_IMPLEMENTED ||
|
||
|
||
(status == STATUS_SUCCESS &&
|
||
(u.streamInformation.StreamNameLength == 0 ||
|
||
(u.streamInformation.StreamNameLength == 14 ))
|
||
)
|
||
|
||
) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_SUBSTREAMS;
|
||
}
|
||
|
||
//
|
||
// Find out if this file is a reparse point
|
||
//
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.tagInformation,
|
||
sizeof( u.tagInformation ),
|
||
FileAttributeTagInformation
|
||
);
|
||
|
||
if( !NT_SUCCESS( status ) ||
|
||
u.tagInformation.ReparseTag == IO_REPARSE_TAG_RESERVED_ZERO ) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_REPARSETAG;
|
||
}
|
||
}
|
||
break;
|
||
|
||
} case ShareTypePipe: {
|
||
|
||
FILE_PIPE_INFORMATION pipeInformation;
|
||
FILE_PIPE_LOCAL_INFORMATION pipeLocalInformation;
|
||
USHORT pipeHandleState;
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeInformation,
|
||
sizeof(pipeInformation),
|
||
FilePipeInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeLocalInformation,
|
||
sizeof(pipeLocalInformation),
|
||
FilePipeLocalInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe local information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Fill in the handle state information in SMB format
|
||
//
|
||
|
||
pipeHandleState = (USHORT)pipeInformation.CompletionMode
|
||
<< PIPE_COMPLETION_MODE_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeEnd
|
||
<< PIPE_PIPE_END_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeType
|
||
<< PIPE_PIPE_TYPE_BITS;
|
||
pipeHandleState |= (USHORT)pipeInformation.ReadMode
|
||
<< PIPE_READ_MODE_BITS;
|
||
pipeHandleState |= (USHORT)(pipeLocalInformation.MaximumInstances &
|
||
0xFF)
|
||
<< PIPE_MAXIMUM_INSTANCES_BITS;
|
||
|
||
SrvFileInformation->HandleState = pipeHandleState;
|
||
|
||
if (pipeLocalInformation.NamedPipeType == FILE_PIPE_MESSAGE_TYPE) {
|
||
SrvFileInformation->Type = FileTypeMessageModePipe;
|
||
} else {
|
||
SrvFileInformation->Type = FileTypeByteModePipe;
|
||
}
|
||
break;
|
||
|
||
} case ShareTypePrint: {
|
||
|
||
SrvFileInformation->Type = FileTypePrinter;
|
||
break;
|
||
|
||
} default:
|
||
|
||
SrvFileInformation->Type = FileTypeUnknown;
|
||
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueryInformationFileAbbreviated
|
||
|
||
NTSTATUS
|
||
SrvQueryNtInformationFile (
|
||
IN HANDLE FileHandle,
|
||
IN PFILE_OBJECT FileObject OPTIONAL,
|
||
IN SHARE_TYPE ShareType,
|
||
IN BOOLEAN AdditionalInfo,
|
||
IN OUT PSRV_NT_FILE_INFORMATION SrvFileInformation
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine makes calls to NtQueryInformationFile to get information
|
||
about a file opened by the server.
|
||
|
||
Arguments:
|
||
|
||
FileHandle - a handle of the file to get information about.
|
||
|
||
FileInformation - pointer to a structure in which to store the
|
||
information.
|
||
|
||
Return Value:
|
||
|
||
A status indicating success or failure of the operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
FILE_PIPE_INFORMATION pipeInformation;
|
||
FILE_PIPE_LOCAL_INFORMATION pipeLocalInformation;
|
||
USHORT pipeHandleState;
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
status = SrvQueryNetworkOpenInformation( FileHandle,
|
||
FileObject,
|
||
&SrvFileInformation->NwOpenInfo,
|
||
FALSE
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
if ( ShareType != ShareTypePipe ) {
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryNtInformationFile: NtQueryInformationFile "
|
||
" failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
}
|
||
return status;
|
||
}
|
||
|
||
if ( ShareType == ShareTypePipe ) {
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeInformation,
|
||
sizeof(pipeInformation),
|
||
FilePipeInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvNtQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
return status;
|
||
}
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&pipeLocalInformation,
|
||
sizeof(pipeLocalInformation),
|
||
FilePipeLocalInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvNtQueryInformationFile: NtQueryInformationFile "
|
||
"(pipe local information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Fill in the handle state information in SMB format
|
||
//
|
||
|
||
pipeHandleState = (USHORT)pipeInformation.CompletionMode
|
||
<< PIPE_COMPLETION_MODE_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeEnd
|
||
<< PIPE_PIPE_END_BITS;
|
||
pipeHandleState |= (USHORT)pipeLocalInformation.NamedPipeType
|
||
<< PIPE_PIPE_TYPE_BITS;
|
||
pipeHandleState |= (USHORT)pipeInformation.ReadMode
|
||
<< PIPE_READ_MODE_BITS;
|
||
pipeHandleState |= (USHORT)(pipeLocalInformation.MaximumInstances &
|
||
0xFF)
|
||
<< PIPE_MAXIMUM_INSTANCES_BITS;
|
||
|
||
SrvFileInformation->HandleState = pipeHandleState;
|
||
} else {
|
||
|
||
SrvFileInformation->HandleState = 0;
|
||
if( AdditionalInfo ) {
|
||
|
||
//
|
||
// the buffer is added to the end to ensure that we have enough space on the
|
||
// stack to return a FILE_STREAM_INFORMATION buffer having the ::$DATA substream
|
||
union {
|
||
FILE_EA_INFORMATION eaInformation;
|
||
FILE_STREAM_INFORMATION streamInformation;
|
||
FILE_ATTRIBUTE_TAG_INFORMATION tagInformation;
|
||
ULONG buffer[ (sizeof( FILE_STREAM_INFORMATION ) + 14) / sizeof(ULONG) ];
|
||
} u;
|
||
|
||
//
|
||
// Find out if this file has EAs
|
||
//
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.eaInformation,
|
||
sizeof( u.eaInformation ),
|
||
FileEaInformation
|
||
);
|
||
if( !NT_SUCCESS( status ) || u.eaInformation.EaSize == 0 ) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_EAS;
|
||
}
|
||
|
||
//
|
||
// Find out if this file has substreams.
|
||
//
|
||
RtlZeroMemory( &u, sizeof(u) );
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.streamInformation,
|
||
sizeof( u ),
|
||
FileStreamInformation
|
||
);
|
||
|
||
//
|
||
// If the filesystem does not support this call, then there are no substreams. Or
|
||
// If the filesystem supports the call but returned exactly no name or returned "::$DATA"
|
||
// then there are no substreams.
|
||
//
|
||
if( status == STATUS_INVALID_PARAMETER ||
|
||
status == STATUS_NOT_IMPLEMENTED ||
|
||
|
||
(status == STATUS_SUCCESS &&
|
||
(u.streamInformation.StreamNameLength == 0 ||
|
||
(u.streamInformation.StreamNameLength == 14 ))
|
||
)
|
||
|
||
) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_SUBSTREAMS;
|
||
}
|
||
|
||
//
|
||
// Find out if this file is a reparse point
|
||
//
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatusBlock,
|
||
(PVOID)&u.tagInformation,
|
||
sizeof( u.tagInformation ),
|
||
FileAttributeTagInformation
|
||
);
|
||
|
||
if( !NT_SUCCESS( status ) ||
|
||
u.tagInformation.ReparseTag == IO_REPARSE_TAG_RESERVED_ZERO ) {
|
||
SrvFileInformation->HandleState |= SMB_FSF_NO_REPARSETAG;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Set the file device type.
|
||
//
|
||
|
||
switch( ShareType ) {
|
||
|
||
case ShareTypeDisk:
|
||
|
||
SrvFileInformation->Type = FileTypeDisk;
|
||
break;
|
||
|
||
case ShareTypePipe:
|
||
|
||
if (pipeLocalInformation.NamedPipeType == FILE_PIPE_MESSAGE_TYPE) {
|
||
SrvFileInformation->Type = FileTypeMessageModePipe;
|
||
} else {
|
||
SrvFileInformation->Type = FileTypeByteModePipe;
|
||
}
|
||
break;
|
||
|
||
case ShareTypePrint:
|
||
|
||
SrvFileInformation->Type = FileTypePrinter;
|
||
break;
|
||
|
||
default:
|
||
|
||
SrvFileInformation->Type = FileTypeUnknown;
|
||
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueryNtInformationFile
|
||
|
||
|
||
NTSTATUS
|
||
SrvQueryDirectoryFile (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN BOOLEAN IsFirstCall,
|
||
IN BOOLEAN FilterLongNames,
|
||
IN BOOLEAN FindWithBackupIntent,
|
||
IN FILE_INFORMATION_CLASS FileInformationClass,
|
||
IN ULONG SearchStorageType,
|
||
IN PUNICODE_STRING FilePathName,
|
||
IN PULONG ResumeFileIndex OPTIONAL,
|
||
IN USHORT SmbSearchAttributes,
|
||
IN PSRV_DIRECTORY_INFORMATION DirectoryInformation,
|
||
IN CLONG BufferLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine acts as a wrapper for NT LanMan server access to
|
||
NtQueryDirectoryFile. It allows server routines to obtain information
|
||
about the files in a directory using the kind of information
|
||
passed in an SMB. This localizes the code for this operation and
|
||
simplifies the writing of SMB processing routines that use wildcards.
|
||
|
||
The calling routine is responsible for setting up a quadword-aligned
|
||
buffer in nonpaged pool that may be used by this routine. A pointer
|
||
to the buffer and the buffer length are passed in as parameters.
|
||
The buffer must be allocated from nonpaged pool because one of
|
||
the things it is used for is as a buffer for NtQueryDirectoryFile,
|
||
a buffered-IO request. The buffer is also used to hold information
|
||
needed by this routine, such as a handle to the directory in which
|
||
the search is being performed, a pointer to the
|
||
FILE_DIRECTORY_INFORMATION structure that was last returned, and the
|
||
basename (with wildcards) that we're using as a search key. Since
|
||
all this information must remain valid across calls to this routine,
|
||
the calling routine must ensure that the buffer remains intact until
|
||
this routine returns an unsuccessful status or STATUS_NO_MORE_FILES,
|
||
or SrvCloseQueryDirectory is called.
|
||
|
||
SMB processing routines which do not need to make use of the Buffer
|
||
field of the outgoing SMB may use this as a buffer for this routine,
|
||
remembering to leave any pathname information in the buffer field of the
|
||
incoming SMB intact by starting the buffer after the pathname. SMB
|
||
processing routines that write into the Buffer field of the outgoing SMB,
|
||
such as Search and Find, must allocate space for the buffer from nonpaged
|
||
pool. The size of the buffer should be approximately 4k. Smaller
|
||
buffers will work, but more slowly due to the need for more calls
|
||
to NtQueryDirectoryFile. The minimum buffer size is equal to:
|
||
|
||
sizeof(SRV_DIRECTORY_INFORMATION) +
|
||
sizeof(SRV_QUERY_DIRECTORY_INFORMATION) +
|
||
MAXIMUM_FILENAME_LENGTH * sizeof(WCHAR) +
|
||
sizeof(UNICODE_STRING) +
|
||
MAXIMUM_FILENAME_LENGTH * sizeof(WCHAR)
|
||
|
||
This ensures that NtQueryDirectoryFile will be able to put at least
|
||
one entry in the buffer.
|
||
|
||
On the first call to this routine, it fills up its buffer with
|
||
information from NtQueryDirectoryFile and passes back the name of
|
||
a single file that conforms to the specified name and search
|
||
attributes. On subsequent calls, the names stored in the buffer are
|
||
used until there are no more files in the directory or another
|
||
call to NtQueryDirectoryFile is needed to again fill the buffer.
|
||
|
||
Whenever the caller is done with the search, it must call
|
||
SrvCloseQueryDirectory. This is required even if this routine
|
||
returns an error.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - pointer to a work context block for the operation. The
|
||
TreeConnect, Session, and RequestHeader fields are used, and the
|
||
pointer is passed to the SMB error handling function if necessary.
|
||
|
||
IsFirstCall - a boolean indicating whether this is the first time
|
||
the calling routine is calling this function. If it is, then
|
||
the directory for the search is opened and other setup
|
||
operations take place.
|
||
|
||
FilterLongNames - a boolean that is TRUE when non-FAT names should be
|
||
filtered out (not returned). If FALSE, return all filenames,
|
||
regardless of whether or not they could be FAT 8.3 names.
|
||
|
||
FindWithBackupIntent - Whether the directory was opened by the use
|
||
for backup intent.
|
||
|
||
FileInformationClass - the type of file structures to return. This
|
||
field can be one of FileDirectoryInformation,
|
||
FileFullDirectoryInformation, FileOleDirectoryInformation, or
|
||
FileBothDirectoryInformation.
|
||
|
||
FilePathName - a pointer to a string describing the file path name
|
||
to do directory searches on. This path is relative to the
|
||
PathName specified in the share block. This parameter is only
|
||
used on the first call to this routine; subsequent calls ignore it.
|
||
|
||
ResumeFileIndex - an optional pointer to a file index which determines
|
||
the file with which to restart the search. NULL if the search
|
||
should be restarted from the last file returned.
|
||
|
||
SmbSearchAttributes - the atttibutes, in SMB format, that files must
|
||
have in order to be found. The search is inclusive, meaning that
|
||
if several attributes are specified, files having those attributes
|
||
will be found, in addition to normal files.
|
||
|
||
DirectoryInformation - a pointer to the buffer to be used by this
|
||
routine to do its work. This buffer must be quadword-aligned.
|
||
|
||
BufferLength - the length of the buffer passed to this routine.
|
||
|
||
Return Value:
|
||
|
||
A status indicating success or failure of the operation, or
|
||
STATUS_NO_MORE_FILES if the files in the directory that match the
|
||
specified parameters have been exausted.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
PFILE_DIRECTORY_INFORMATION *currentEntry;
|
||
ULONG inclusiveSearchAttributes;
|
||
ULONG exclusiveSearchAttributes;
|
||
ULONG currentAttributes;
|
||
BOOLEAN returnDirectories;
|
||
BOOLEAN returnDirectoriesOnly;
|
||
BOOLEAN calledQueryDirectory = FALSE;
|
||
|
||
OBJECT_ATTRIBUTES objectAttributes;
|
||
PUNICODE_STRING filePathName;
|
||
BOOLEAN FreePathName = FALSE;
|
||
UNICODE_STRING objectNameString;
|
||
UNICODE_STRING baseFileName;
|
||
PSHARE fileShare = NULL;
|
||
|
||
PUNICODE_STRING resumeName = NULL;
|
||
BOOLEAN resumeSearch;
|
||
|
||
CLONG fileNameOffset;
|
||
ULONG createOptions;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ASSERT( ( FileInformationClass == FileDirectoryInformation ) ||
|
||
( FileInformationClass == FileFullDirectoryInformation ) ||
|
||
( FileInformationClass == FileBothDirectoryInformation ) ||
|
||
( FileInformationClass == FileIdFullDirectoryInformation ) ||
|
||
( FileInformationClass == FileIdBothDirectoryInformation ) );
|
||
|
||
//
|
||
// Set up the offsets to the fields in FILE_FULL_DIR_INFORMATION in
|
||
// a different place than corresponding fields in
|
||
// FILE_DIRECTORY_INFORMATION. These allow this routine to
|
||
// efficiently use either structure.
|
||
//
|
||
|
||
{
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, NextEntryOffset ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, NextEntryOffset ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileIndex ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileIndex ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, CreationTime ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, CreationTime ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, LastAccessTime ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, LastAccessTime ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, LastWriteTime ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, LastWriteTime ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, ChangeTime ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, ChangeTime ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, EndOfFile ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, EndOfFile ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, AllocationSize ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, AllocationSize ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileAttributes ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileAttributes ) );
|
||
C_ASSERT( FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileNameLength ) ==
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileNameLength ) );
|
||
}
|
||
|
||
if ( FileInformationClass == FileFullDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileName[0] );
|
||
} else if ( FileInformationClass == FileBothDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, FileName[0] );
|
||
} else if ( FileInformationClass == FileIdBothDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_ID_BOTH_DIR_INFORMATION, FileName[0] );
|
||
} else if ( FileInformationClass == FileIdFullDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_ID_FULL_DIR_INFORMATION, FileName[0] );
|
||
} else {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileName[0] );
|
||
}
|
||
|
||
//
|
||
// This macro is used to actually get at the FileName field. Note
|
||
// that it depends on a local variable.
|
||
//
|
||
|
||
#define FILE_NAME(a) (PWCH)( (PCHAR)(a) + fileNameOffset )
|
||
|
||
//
|
||
// If this is the first call to this routine, we must open the
|
||
// correct directory, thereby obtaining a handle to it to pass to
|
||
// NtQueryDirectoryFile. The calling routine stores the handle
|
||
// to prevent problems if SrvQueryDirectoryFile is called more
|
||
// than once simultaneously.
|
||
//
|
||
|
||
if ( IsFirstCall ) {
|
||
|
||
BOOLEAN endsInDot;
|
||
ULONG attributes;
|
||
|
||
DirectoryInformation->DirectoryHandle = 0L;
|
||
DirectoryInformation->ErrorOnFileOpen = FALSE;
|
||
DirectoryInformation->OnlySingleEntries = FALSE;
|
||
|
||
//
|
||
// We must get the appropriate directory name in which to perform the
|
||
// search. First, find the basename of the file from the FilePathName.
|
||
//
|
||
// Find out whether there are wildcards in the file name we are
|
||
// searching for. This information will be used later to
|
||
// know whether we should try to get more files if the buffer
|
||
// is empty--if there were no wildcards and we have emptied the
|
||
// buffer, then we know that we have already returned the one and
|
||
// only file that could be found, so return STATUS_NO_MORE_FILES.
|
||
//
|
||
|
||
SrvGetBaseFileName( FilePathName, &baseFileName );
|
||
DirectoryInformation->Wildcards =
|
||
FsRtlDoesNameContainWildCards( &baseFileName );
|
||
|
||
if ( DirectoryInformation->Wildcards &&
|
||
(!IS_NT_DIALECT(WorkContext->Connection->SmbDialect) ) ) {
|
||
|
||
//
|
||
// Bogus code to workaround ~* problem
|
||
//
|
||
|
||
if ( baseFileName.Buffer[(baseFileName.Length>>1)-1] == (WCHAR)'.' ) {
|
||
endsInDot = TRUE;
|
||
baseFileName.Length -= sizeof( WCHAR );
|
||
} else {
|
||
endsInDot = FALSE;
|
||
}
|
||
|
||
//
|
||
// Convert the file name to the new form expected by the file
|
||
// systems. Special case *.* to * since it is so common. Otherwise
|
||
// transmogrify the input name according to the following rules:
|
||
//
|
||
// - Change all ? to DOS_QM
|
||
// - Change all . followed by ? or * to DOS_DOT
|
||
// - Change all * followed by a . into DOS_STAR
|
||
//
|
||
// These transmogrifications are all done in place.
|
||
//
|
||
|
||
if ( (baseFileName.Length == 6) &&
|
||
(RtlEqualMemory(baseFileName.Buffer, StrStarDotStar, 6) ) ) {
|
||
|
||
baseFileName.Length = 2;
|
||
|
||
} else {
|
||
|
||
ULONG index;
|
||
WCHAR *nameChar;
|
||
|
||
for ( index = 0, nameChar = baseFileName.Buffer;
|
||
index < baseFileName.Length/sizeof(WCHAR);
|
||
index += 1, nameChar += 1) {
|
||
|
||
if (index && (*nameChar == L'.') && (*(nameChar - 1) == L'*')) {
|
||
|
||
*(nameChar - 1) = DOS_STAR;
|
||
}
|
||
|
||
if ((*nameChar == L'?') || (*nameChar == L'*')) {
|
||
|
||
if (*nameChar == L'?') {
|
||
*nameChar = DOS_QM;
|
||
}
|
||
|
||
if (index && *(nameChar-1) == L'.') {
|
||
*(nameChar-1) = DOS_DOT;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( endsInDot && *(nameChar - 1) == L'*' ) {
|
||
*(nameChar-1) = DOS_STAR;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set up the object attributes structure for SrvIoCreateFile.
|
||
//
|
||
|
||
objectNameString.Buffer = FilePathName->Buffer;
|
||
objectNameString.Length = SrvGetSubdirectoryLength( FilePathName );
|
||
objectNameString.MaximumLength = objectNameString.Length;
|
||
|
||
//
|
||
// !!! If the object system supported relative opens with name
|
||
// length = 0, this wouldn't be necessary. Take it out when
|
||
// the object system is done.
|
||
//
|
||
|
||
|
||
if ( objectNameString.Length == 0 ) {
|
||
|
||
//
|
||
// Since we are opening the root directory, set the attribute
|
||
// to case insensitive since this is how we opened the share
|
||
// point when it was added.
|
||
//
|
||
|
||
PSHARE share = WorkContext->TreeConnect->Share;
|
||
|
||
status = SrvSnapGetNameString( WorkContext, &filePathName, &FreePathName );
|
||
if( !NT_SUCCESS(status) )
|
||
{
|
||
return status;
|
||
}
|
||
objectNameString = *filePathName;
|
||
|
||
DirectoryInformation->Wildcards = TRUE;
|
||
attributes = OBJ_CASE_INSENSITIVE;
|
||
|
||
} else {
|
||
|
||
fileShare = WorkContext->TreeConnect->Share;
|
||
attributes =
|
||
(WorkContext->RequestHeader->Flags & SMB_FLAGS_CASE_INSENSITIVE ||
|
||
WorkContext->Session->UsingUppercasePaths) ?
|
||
OBJ_CASE_INSENSITIVE : 0L;
|
||
|
||
}
|
||
|
||
SrvInitializeObjectAttributes_U(
|
||
&objectAttributes,
|
||
&objectNameString,
|
||
attributes,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint1( "Opening directory name: %wZ\n", &objectNameString );
|
||
}
|
||
|
||
//
|
||
// Attempt to open the directory, using the client's security
|
||
// profile to check access. (We call SrvIoCreateFile, rather than
|
||
// NtOpenFile, in order to get user-mode access checking.)
|
||
//
|
||
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpenAttempts );
|
||
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalOpensForPathOperations );
|
||
//
|
||
// There's no need to specify FILE_DIRECTORY_FILE; the file systems
|
||
// will open whatever is there and reject later QueryDirectoryFile
|
||
// when the object opened does not support enumeration.
|
||
//
|
||
|
||
createOptions = 0;
|
||
if (FindWithBackupIntent) {
|
||
createOptions = FILE_OPEN_FOR_BACKUP_INTENT;
|
||
}
|
||
|
||
status = SrvIoCreateFile(
|
||
WorkContext,
|
||
&DirectoryInformation->DirectoryHandle,
|
||
FILE_LIST_DIRECTORY, // DesiredAccess
|
||
&objectAttributes,
|
||
&ioStatusBlock,
|
||
NULL, // AllocationSize
|
||
0, // FileAttributes
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||
FILE_OPEN, // Disposition
|
||
createOptions, // CreateOptions
|
||
NULL, // EaBuffer
|
||
0, // EaLength
|
||
CreateFileTypeNone, // File type
|
||
NULL, // ExtraCreateParameters
|
||
IO_FORCE_ACCESS_CHECK, // Options
|
||
fileShare
|
||
);
|
||
|
||
//
|
||
// If the user didn't have this permission, update the statistics
|
||
// database.
|
||
//
|
||
|
||
if ( status == STATUS_ACCESS_DENIED ) {
|
||
SrvStatistics.AccessPermissionErrors++;
|
||
}
|
||
|
||
// Free the path since we won't use it anymore
|
||
if( FreePathName )
|
||
{
|
||
FREE_HEAP( filePathName );
|
||
filePathName = NULL;
|
||
}
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
IF_DEBUG(ERRORS) {
|
||
SrvPrint2( "SrvQueryDirectoryFile: SrvIoCreateFile for dir %wZ "
|
||
"failed: %X\n",
|
||
&objectNameString, status );
|
||
}
|
||
DirectoryInformation->DirectoryHandle = NULL;
|
||
return status;
|
||
}
|
||
|
||
SRVDBG_CLAIM_HANDLE( DirectoryInformation->DirectoryHandle, "DID", 3, DirectoryInformation );
|
||
SrvStatistics.TotalFilesOpened++;
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint1( "SrvIoCreateFile succeeded, handle = %p\n",
|
||
DirectoryInformation->DirectoryHandle );
|
||
}
|
||
|
||
//
|
||
// Set up the currentEntry pointer. This is a pointer to the
|
||
// location where the FILE_DIRECTORY_INFORMATION pointer is
|
||
// stored. It is not really necessary--
|
||
// DirectoryInformation->CurrentEntry could be substituted for
|
||
// every occurrance of *currentEntry. Using currentEntry makes
|
||
// the code more compact and simpler.
|
||
//
|
||
|
||
currentEntry = &(DirectoryInformation->CurrentEntry);
|
||
*currentEntry = NULL;
|
||
|
||
//
|
||
// Store the length of buffer space remaining--this is where IO
|
||
// request information will be stored.
|
||
//
|
||
|
||
DirectoryInformation->BufferLength = BufferLength -
|
||
sizeof(SRV_DIRECTORY_INFORMATION);
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint3( "In BufferLength: %ld, sizeof(): %ld, ->BufferLength: "
|
||
"%ld\n", BufferLength,
|
||
sizeof(SRV_DIRECTORY_INFORMATION),
|
||
DirectoryInformation->BufferLength );
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// This is not the first call to this routine, so just set up
|
||
// the currentEntry pointer and have it point to the next entry
|
||
// in the buffer. If there are no more entries in the buffer at
|
||
// this time (NextEntryOffset == 0), set the currentEntry
|
||
// pointer to NULL so that we will know to get more later.
|
||
//
|
||
|
||
currentEntry = &DirectoryInformation->CurrentEntry;
|
||
|
||
if ( *currentEntry != NULL ) {
|
||
|
||
if ( (*currentEntry)->NextEntryOffset == 0 ) {
|
||
|
||
*currentEntry = NULL;
|
||
|
||
} else {
|
||
|
||
*currentEntry = (PFILE_DIRECTORY_INFORMATION)
|
||
( (PCHAR)*currentEntry + (*currentEntry)->NextEntryOffset );
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// The lower byte of SmbSearchAttributes defines "inclusive"
|
||
// search attribute bits, meaning that if the bit is on on the
|
||
// file but not set in the request, the file should be skipped.
|
||
// For example, if HIDDEN is not specified in the request, then
|
||
// files with the HIDDEN bit turned on are not returned.
|
||
//
|
||
// The upper byte of SmbSearchAttributes, as an LM2.1 extension,
|
||
// defines "exclusive" search attributes, which means that a
|
||
// file must have the specified bits set in order to be returned.
|
||
// For example, if the READONLY bit is set in the request, only
|
||
// files with the READONLY bit turned on will be returned to the
|
||
// client.
|
||
//
|
||
// Convert the inclusive and exclusive search bits to NT format.
|
||
//
|
||
|
||
SRV_SMB_ATTRIBUTES_TO_NT(
|
||
(USHORT)(SmbSearchAttributes & 0xFF),
|
||
&returnDirectories,
|
||
&inclusiveSearchAttributes
|
||
);
|
||
|
||
SRV_SMB_ATTRIBUTES_TO_NT(
|
||
(USHORT)(SmbSearchAttributes >> 8),
|
||
&returnDirectoriesOnly,
|
||
&exclusiveSearchAttributes
|
||
);
|
||
|
||
//
|
||
// For the inclusive bits, files with the NORMAL, ARCHIVE, or READONLY
|
||
// bits set should be returned regardless of whether these bits
|
||
// were set in SmbSearchAttributes.
|
||
//
|
||
|
||
inclusiveSearchAttributes |= FILE_ATTRIBUTE_NORMAL |
|
||
FILE_ATTRIBUTE_ARCHIVE |
|
||
FILE_ATTRIBUTE_READONLY;
|
||
|
||
//
|
||
// For exclusive bits, the VOLUME bit is meaningless. It is also not
|
||
// necessary for a file to have the NORMAL bit on, since the NORMAL
|
||
// bit is not defined for the SMB protocol.
|
||
//
|
||
|
||
exclusiveSearchAttributes &=
|
||
~(SMB_FILE_ATTRIBUTE_VOLUME | FILE_ATTRIBUTE_NORMAL);
|
||
|
||
//
|
||
// If a resume file index was passed in, this search is a resumption
|
||
// from that file and the name specified in FilePathName.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( ResumeFileIndex ) ) {
|
||
|
||
resumeSearch = TRUE;
|
||
resumeName = FilePathName;
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint3( "Resuming search at file %wZ, length %ld, index %lx\n",
|
||
resumeName, resumeName->Length,
|
||
*ResumeFileIndex );
|
||
}
|
||
|
||
} else {
|
||
|
||
resumeSearch = FALSE;
|
||
}
|
||
|
||
//
|
||
// Now we need to find a file to return. We keep going until we find
|
||
// a file that meets all of our criteria, pointing to the next file
|
||
// if a file fails. We continue the loop under the following conditions:
|
||
//
|
||
// 1) If *currentEntry == NULL, then we haven't yet filled our buffer
|
||
// with entries, so get some entries.
|
||
//
|
||
// 2) If there are bits set in the FileAttributes field of the
|
||
// FILE_DIRECTORY_INFORMATION field that are not set in the
|
||
// searchAttributes variable, then the file does not meet the
|
||
// search requirements, and we need to continue looking.
|
||
//
|
||
// 3) If we are not searching for directories and the file is actually
|
||
// a directory, skip over it.
|
||
//
|
||
// 4) If we are filtering long (non-FAT) filenames AND this file name
|
||
// is not a legal FAT name AND we have no short name for this file,
|
||
// skip it.
|
||
//
|
||
// 5) If the file doesn't have attribute bits specified as exclusive
|
||
// bits, skip it.
|
||
//
|
||
// 6) If the file is not a directory and we're only supposed to return
|
||
// directories, skip it.
|
||
//
|
||
// When this loop is complete, *currentEntry will point to the
|
||
// FILE_DIRECTORY_INFORMATION structure corresponding to the file we
|
||
// will return. If no qualifying files are found, return
|
||
// STATUS_NO_MORE_FILES and close the directory.
|
||
//
|
||
|
||
if( *currentEntry != NULL ) {
|
||
SRV_NT_ATTRIBUTES_TO_SMB( (*currentEntry)->FileAttributes,0,¤tAttributes);
|
||
}
|
||
|
||
while ( ( *currentEntry == NULL ) // 1
|
||
|
||
||
|
||
|
||
( (currentAttributes | inclusiveSearchAttributes) != // 2
|
||
inclusiveSearchAttributes )
|
||
|
||
||
|
||
|
||
( !returnDirectories && // 3
|
||
(currentAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||
|
||
||
|
||
// 4
|
||
( FilterLongNames &&
|
||
!SrvIsLegalFatName( FILE_NAME( *currentEntry ),
|
||
(*currentEntry)->FileNameLength) &&
|
||
!( FileInformationClass == FileBothDirectoryInformation &&
|
||
((PFILE_BOTH_DIR_INFORMATION)*currentEntry)->
|
||
ShortNameLength != 0) )
|
||
|
||
|
||
||
|
||
// 5
|
||
( (currentAttributes | exclusiveSearchAttributes) !=
|
||
currentAttributes )
|
||
|
||
||
|
||
|
||
( returnDirectoriesOnly && // 6
|
||
!(currentAttributes & FILE_ATTRIBUTE_DIRECTORY) )
|
||
|
||
) {
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
if ( *currentEntry != NULL) {
|
||
UNICODE_STRING name;
|
||
name.Length = (SHORT)(*currentEntry)->FileNameLength;
|
||
name.Buffer = FILE_NAME( *currentEntry );
|
||
SrvPrint4( "Skipped %wZ, FileAttr: %lx, ISA: %lx ESA: %lx ",
|
||
&name, (*currentEntry)->FileAttributes,
|
||
inclusiveSearchAttributes,
|
||
exclusiveSearchAttributes );
|
||
SrvPrint4( "NL=%ld D=%ld RD=%ld RDO=%ld ",
|
||
(*currentEntry)->FileNameLength,
|
||
(((*currentEntry)->FileAttributes &
|
||
FILE_ATTRIBUTE_DIRECTORY) != 0), returnDirectories,
|
||
returnDirectoriesOnly );
|
||
SrvPrint1( "FLN=%ld\n", FilterLongNames );
|
||
}
|
||
}
|
||
|
||
//
|
||
// We need to look for more files under the following conditions:
|
||
//
|
||
// o we have yet to fill the buffer with entries;
|
||
//
|
||
// o the NextEntryOffset is zero, indicating that the files in
|
||
// the buffer have been exausted.
|
||
//
|
||
|
||
if ( *currentEntry == NULL ||
|
||
(*currentEntry)->NextEntryOffset == 0 ) {
|
||
|
||
PUNICODE_STRING actualString;
|
||
BOOLEAN bruteForceRewind = FALSE;
|
||
|
||
//
|
||
// The buffer has no more valid entries in it. If no
|
||
// wildcards were specified in the file name to search on,
|
||
// then we have already returned the single file and we
|
||
// should just stop now. Otherwise, we go get more entries.
|
||
//
|
||
|
||
if ( !DirectoryInformation->Wildcards &&
|
||
( !IsFirstCall || calledQueryDirectory ) ) {
|
||
|
||
if ( calledQueryDirectory ) {
|
||
return STATUS_NO_SUCH_FILE;
|
||
} else {
|
||
return STATUS_NO_MORE_FILES;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set up the file name that will be passed to
|
||
// SrvIssueQueryDirectoryRequest. If this is the first
|
||
// call, then pass the file spec given by the user. If this
|
||
// is a resume search and we haven't yet done a directory
|
||
// query, then use the resume file name and index.
|
||
// Otherwise, pass NULL for these and the file system will
|
||
// continue from where it left off after the last directory
|
||
// query.
|
||
//
|
||
|
||
if ( IsFirstCall &&
|
||
!calledQueryDirectory &&
|
||
baseFileName.Length != 0 ) {
|
||
|
||
actualString = &baseFileName;
|
||
|
||
} else if ( resumeSearch && !calledQueryDirectory ) {
|
||
|
||
actualString = resumeName;
|
||
|
||
} else {
|
||
|
||
actualString = NULL;
|
||
ResumeFileIndex = NULL;
|
||
|
||
}
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
|
||
if ( actualString == NULL ) {
|
||
SrvPrint0( "**** CALLING NTQUERYDIRECTORYFILE, file = NULL, length: 0\n" );
|
||
} else {
|
||
SrvPrint2( "**** CALLING NTQUERYDIRECTORYFILE, file = %wZ, length: %ld\n",
|
||
actualString, actualString->Length );
|
||
}
|
||
|
||
SrvPrint0( "Reason: \n" );
|
||
|
||
if ( *currentEntry == NULL ) {
|
||
SrvPrint0( "*currentEntry == NULL\n" );
|
||
} else {
|
||
SrvPrint1( "(*currentEntry)->NextEntryOffset == %ld\n",
|
||
(*currentEntry)->NextEntryOffset );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Do the directory query operation using a directly-built
|
||
// IRP. Doing this rather than calling NtQueryDirectoryFile
|
||
// eliminates a buffered I/O copy of the directory
|
||
// information and allows use of a kernel event object. If
|
||
// this is the first call to NtQueryDirectoryFile, pass it
|
||
// the search file name. If this is a rewind or resume of a
|
||
// prior search, pass the resume file name and index.
|
||
//
|
||
// The query is performed synchronously, which may be a
|
||
// detriment to performance. However, it may be the case
|
||
// that routines calling SrvQueryDirectoryFile want to
|
||
// exploit the asynchronous capabilities of the IO system,
|
||
// so keeping this routine synchronous significantly
|
||
// simplifies their job.
|
||
//
|
||
|
||
status = SrvIssueQueryDirectoryRequest(
|
||
DirectoryInformation->DirectoryHandle,
|
||
(PCHAR)DirectoryInformation->Buffer,
|
||
DirectoryInformation->BufferLength,
|
||
FileInformationClass,
|
||
actualString,
|
||
ResumeFileIndex,
|
||
FALSE,
|
||
DirectoryInformation->OnlySingleEntries
|
||
);
|
||
|
||
calledQueryDirectory = TRUE;
|
||
|
||
//
|
||
// If the file system cannot support the rewind request,
|
||
// do a brute force rewind (restart search at beginning
|
||
// of directory).
|
||
//
|
||
// This check is before the check for STATUS_NO_MORE_FILES
|
||
// in case there are no files after the resume file.
|
||
//
|
||
|
||
if ( status == STATUS_NOT_IMPLEMENTED ) {
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint0( "Doing brute force rewind!!\n" );
|
||
}
|
||
|
||
bruteForceRewind = TRUE;
|
||
DirectoryInformation->OnlySingleEntries = TRUE;
|
||
|
||
status = BruteForceRewind(
|
||
DirectoryInformation->DirectoryHandle,
|
||
(PCHAR)DirectoryInformation->Buffer,
|
||
DirectoryInformation->BufferLength,
|
||
actualString,
|
||
FileInformationClass,
|
||
currentEntry
|
||
);
|
||
|
||
//
|
||
// If BruteForceRewind fails with STATUS_NOT_IMPLEMENTED, it
|
||
// means that the client requested a rewind from a
|
||
// non-existant file. The only time this happens in when
|
||
// an OS/2 is deleting many files in a directory. To cope
|
||
// simple rewind the search to the beginning of the
|
||
// directory.
|
||
//
|
||
|
||
if ( status == STATUS_NOT_IMPLEMENTED ) {
|
||
|
||
bruteForceRewind = FALSE;
|
||
DirectoryInformation->OnlySingleEntries = FALSE;
|
||
|
||
status = SrvIssueQueryDirectoryRequest(
|
||
DirectoryInformation->DirectoryHandle,
|
||
(PCHAR)DirectoryInformation->Buffer,
|
||
DirectoryInformation->BufferLength,
|
||
FileInformationClass,
|
||
actualString,
|
||
ResumeFileIndex,
|
||
TRUE,
|
||
FALSE
|
||
);
|
||
}
|
||
}
|
||
|
||
//
|
||
// If there are no more files to be gotten, then stop.
|
||
//
|
||
|
||
if ( status == STATUS_NO_MORE_FILES ) {
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint0( "SrvQueryDirectoryFile: No more files.\n" );
|
||
}
|
||
return status;
|
||
}
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint1( "SrvQueryDirectoryFile: NtQueryDirectoryFile "
|
||
"failed: %X.\n", status );
|
||
}
|
||
return status;
|
||
}
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint1( "NtQueryDirectoryFile succeeded: %X\n", status );
|
||
}
|
||
|
||
//
|
||
// If there wasn't a brute force rewind, which would have
|
||
// set up the CurrentEntry pointer, Set up CurrentEntry
|
||
// pointer to point to the first entry in the buffer.
|
||
//
|
||
|
||
if ( !bruteForceRewind ) {
|
||
*currentEntry =
|
||
(PFILE_DIRECTORY_INFORMATION)DirectoryInformation->Buffer;
|
||
} else {
|
||
bruteForceRewind = FALSE;
|
||
}
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
UNICODE_STRING name;
|
||
name.Length = (SHORT)(*currentEntry)->FileNameLength;
|
||
name.Buffer = FILE_NAME( *currentEntry );
|
||
SrvPrint2( "First file name is %wZ, length = %ld\n",
|
||
&name, (*currentEntry)->FileNameLength );
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// The file described by the FILE_DIRECTORY_INFORMATION pointed
|
||
// to by *currentEntry does not meet our requirements, so
|
||
// point to the next file in the buffer.
|
||
//
|
||
|
||
*currentEntry = (PFILE_DIRECTORY_INFORMATION)( (PCHAR)*currentEntry
|
||
+ (*currentEntry)->NextEntryOffset );
|
||
}
|
||
|
||
if( *currentEntry != NULL ) {
|
||
SRV_NT_ATTRIBUTES_TO_SMB( (*currentEntry)->FileAttributes,0,¤tAttributes);
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueryDirectoryFile
|
||
|
||
|
||
STATIC
|
||
NTSTATUS
|
||
BruteForceRewind(
|
||
IN HANDLE DirectoryHandle,
|
||
IN PVOID Buffer,
|
||
IN ULONG BufferLength,
|
||
IN PUNICODE_STRING FileName,
|
||
IN FILE_INFORMATION_CLASS FileInformationClass,
|
||
IN OUT PFILE_DIRECTORY_INFORMATION *CurrentEntry
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine manually does a rewind rather than use the file system
|
||
to do it. It gets starts at the first entry in the directory
|
||
specified by DirectoryHandle and continues until it reaches the end
|
||
of the directory or a match. If a file is deleted between the
|
||
original search and the rewind, then this mechanism will fail.
|
||
|
||
This routine is intended to work in conjunction with
|
||
SrvQueryDirectoryFile.
|
||
|
||
|
||
Arguments:
|
||
|
||
DirectoryHandle - handle of directory to search.
|
||
|
||
Buffer - Space to hold results.
|
||
|
||
BufferLength - length of Buffer.
|
||
|
||
FileName - the rewind file name. The file *after* this one is returned.
|
||
|
||
FileInformationClass - one of FileDirectoryInformation,
|
||
FileBothDirInformation, or FileFullDirectoryInformation.
|
||
(The latter of the four if EA sizes are being requested.)
|
||
|
||
CurrentEntry - a pointer to receive a pointer to the file after
|
||
FileName in the directory.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - result of operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
UNICODE_STRING checkFileName;
|
||
BOOLEAN matchFound = FALSE;
|
||
BOOLEAN restartScan = TRUE;
|
||
|
||
ULONG fileNameOffset;
|
||
|
||
PAGED_CODE( );
|
||
|
||
checkFileName.Length = 0;
|
||
*CurrentEntry = NULL;
|
||
|
||
if ( FileInformationClass == FileFullDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_FULL_DIR_INFORMATION, FileName[0] );
|
||
} else if ( FileInformationClass == FileBothDirectoryInformation ) {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION, FileName[0] );
|
||
} else {
|
||
fileNameOffset =
|
||
FIELD_OFFSET( FILE_DIRECTORY_INFORMATION, FileName[0] );
|
||
}
|
||
|
||
while ( TRUE ) {
|
||
|
||
if ( *CurrentEntry == NULL ) {
|
||
|
||
//
|
||
// Restart the directory search and get a buffer of files.
|
||
//
|
||
|
||
status = SrvIssueQueryDirectoryRequest(
|
||
DirectoryHandle,
|
||
Buffer,
|
||
BufferLength,
|
||
FileInformationClass,
|
||
NULL,
|
||
NULL,
|
||
restartScan,
|
||
TRUE
|
||
);
|
||
|
||
restartScan = FALSE;
|
||
|
||
if ( status == STATUS_NO_MORE_FILES ) {
|
||
|
||
if ( matchFound ) {
|
||
|
||
//
|
||
// The file matched the last one in the directory;
|
||
// there is no following file. Return
|
||
// STATUS_NO_MORE_FILES.
|
||
//
|
||
|
||
return status;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The file was deleted between when the original search
|
||
// was done and this rewind. Return an error.
|
||
//
|
||
|
||
return STATUS_NOT_IMPLEMENTED;
|
||
}
|
||
}
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Set up the current entry pointer.
|
||
//
|
||
|
||
*CurrentEntry = Buffer;
|
||
}
|
||
|
||
//
|
||
// If the last file we looked at was the correct resume file,
|
||
// then we want to return this file.
|
||
//
|
||
|
||
if ( matchFound ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Check to see if this file is the resume file.
|
||
//
|
||
|
||
checkFileName.Length = (SHORT)(*CurrentEntry)->FileNameLength;
|
||
checkFileName.Buffer = FILE_NAME( *CurrentEntry );
|
||
checkFileName.MaximumLength = checkFileName.Length;
|
||
|
||
if ( RtlCompareUnicodeString(
|
||
FileName,
|
||
&checkFileName,
|
||
TRUE
|
||
) == 0 ) {
|
||
matchFound = TRUE;
|
||
|
||
} else if ( FileInformationClass == FileBothDirectoryInformation ) {
|
||
|
||
//
|
||
// Compare the short name.
|
||
//
|
||
|
||
checkFileName.Length = (SHORT)
|
||
((PFILE_BOTH_DIR_INFORMATION)*CurrentEntry)->ShortNameLength;
|
||
checkFileName.Buffer =
|
||
((PFILE_BOTH_DIR_INFORMATION)*CurrentEntry)->ShortName;
|
||
checkFileName.MaximumLength = checkFileName.Length;
|
||
|
||
if ( RtlCompareUnicodeString(
|
||
FileName,
|
||
&checkFileName,
|
||
TRUE
|
||
) == 0 ) {
|
||
matchFound = TRUE;
|
||
}
|
||
}
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
if ( matchFound ) {
|
||
SrvPrint2( "Matched: %wZ and %wZ\n", FileName, &checkFileName );
|
||
} else {
|
||
SrvPrint2( "No match: %wZ and %wZ\n", FileName, &checkFileName );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set up the current entry pointer for the next iteration.
|
||
//
|
||
|
||
if ( (*CurrentEntry)->NextEntryOffset == 0 ) {
|
||
*CurrentEntry = NULL;
|
||
} else {
|
||
*CurrentEntry =
|
||
(PFILE_DIRECTORY_INFORMATION)( (PCHAR)(*CurrentEntry) +
|
||
(*CurrentEntry)->NextEntryOffset );
|
||
}
|
||
}
|
||
|
||
} // BruteForceRewind
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SrvQueryEaFile (
|
||
IN BOOLEAN IsFirstCall,
|
||
IN HANDLE FileHandle,
|
||
IN PFILE_GET_EA_INFORMATION EaList OPTIONAL,
|
||
IN ULONG EaListLength,
|
||
IN PSRV_EA_INFORMATION EaInformation,
|
||
IN CLONG BufferLength,
|
||
OUT PULONG EaErrorOffset
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine acts as a wrapper for NT LanMan server access to
|
||
NtQueryEaFile. It has basically the same interface as
|
||
SrvQueryDirectoryFile, allowing a routine to be written to deal
|
||
with a single EA at a time while also maintaining performance
|
||
by requesting a large number of EAs from the IO system at a
|
||
time.
|
||
|
||
The calling routine is responsible for setting up a quadword-aligned
|
||
buffer in nonpaged pool that may be used by this routine. A pointer
|
||
to the buffer and the buffer length are passed in as parameters.
|
||
The buffer must be allocated from nonpaged pool because one of
|
||
the things it is used for is as a buffer for NtQueryEaFile,
|
||
a buffered-IO request. The buffer is also used to hold information
|
||
needed by this routine, such as a pointer to the FILE_EA_INFORMATION
|
||
structure that was last returned. Since all this information must
|
||
remain valid across calls to this routine, the calling routine
|
||
must ensure that the buffer remains intact until this routine
|
||
returns an unsuccessful status or STATUS_NO_MORE_EAS.
|
||
|
||
Routines that make use of this routine should set up a buffer
|
||
large enough to hold at least a single EA. Since this can be
|
||
over 64k, it is a good idea to call NtQueryInformationFile to
|
||
get the EA size, then allocate a buffer of this size, unless
|
||
it is greater than the maximum size of an EA. In this case,
|
||
the maximum size of an EA should be allocated as the buffer.
|
||
|
||
On the first call to this routine, it fills up its buffer with
|
||
information from NtQueryEaFile and passes back a single EA. On
|
||
subsequent calls, the names stored in the buffer are used until
|
||
there are no more files in the directory or another call to
|
||
NtQueryEaFile is needed to again fill the buffer.
|
||
|
||
Arguments:
|
||
|
||
IsFirstCall - a boolean indicating whether this is the first time
|
||
the calling routine is calling this function. If it is, then
|
||
setup operations take place.
|
||
|
||
FileHandle - a handle to a file open with FILE_READ_EA.
|
||
|
||
EaList - an optional pointer to an NT-style get EA list. Only those
|
||
EAs listed in this structure are returned.
|
||
|
||
EaListLength - length in bytes of ths get EA list.
|
||
|
||
EaInformation - a pointer to the buffer to be used by this routine
|
||
to do its work. This buffer must be quadword-aligned.
|
||
|
||
BufferLength - the length of the buffer passed to this routine.
|
||
|
||
EaErrorOffset - the offset into EaList of an invalid EA, if any.
|
||
|
||
Return Value:
|
||
|
||
A status indicating success or failure of the operation, or
|
||
STATUS_NO_MORE_EAS if all the EAs have been returned.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
PFILE_GET_EA_INFORMATION useEaList = NULL;
|
||
PFILE_FULL_EA_INFORMATION *currentEntry;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If this is the first call, do the necessary setup.
|
||
//
|
||
|
||
if ( IsFirstCall ) {
|
||
|
||
//
|
||
// Set up the currentEntry pointer. This is a pointer to the
|
||
// location where the FILE_EA_INFORMATION pointer is stored.
|
||
// It is not really necessary--EaInformation->CurrentEntry
|
||
// could be substituted for every occurrance of *currentEntry.
|
||
// Using currentEntry makes the code more compact and simpler.
|
||
//
|
||
|
||
currentEntry = &(EaInformation->CurrentEntry);
|
||
*currentEntry = NULL;
|
||
|
||
//
|
||
// Store the length of buffer space remaining--this is where IO
|
||
// request information will be stored.
|
||
//
|
||
|
||
EaInformation->BufferLength = BufferLength - sizeof(SRV_EA_INFORMATION);
|
||
EaInformation->GetEaListOffset = 0;
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint3( "In BufferLength: %ld, sizeof(): %ld, ->BufferLength: "
|
||
"%ld\n", BufferLength, sizeof(SRV_EA_INFORMATION),
|
||
EaInformation->BufferLength );
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// This is not the first call to this routine, so just set up
|
||
// the currentEntry pointer and have it point to the next entry
|
||
// in the buffer. If there are no more entries in the buffer at
|
||
// this time (NextEntryOffset == 0), set the currentEntry
|
||
// pointer to NULL so that we will know to get more later.
|
||
//
|
||
|
||
currentEntry = &EaInformation->CurrentEntry;
|
||
|
||
if ( *currentEntry != NULL ) {
|
||
|
||
if ( (*currentEntry)->NextEntryOffset == 0 ) {
|
||
|
||
*currentEntry = NULL;
|
||
|
||
} else {
|
||
|
||
*currentEntry = (PFILE_FULL_EA_INFORMATION)
|
||
( (PCHAR)*currentEntry + (*currentEntry)->NextEntryOffset );
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the buffer has no valid entries in it, get some.
|
||
//
|
||
|
||
if ( *currentEntry == NULL ) {
|
||
|
||
//
|
||
// If all the EAs in a get EA list were returned last time,
|
||
// return now.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT(EaList) &&
|
||
EaInformation->GetEaListOffset == 0xFFFFFFFF ) {
|
||
|
||
return STATUS_NO_MORE_EAS;
|
||
}
|
||
|
||
//
|
||
// The buffer has no more valid entries in it, so get more.
|
||
//
|
||
|
||
IF_DEBUG(SEARCH) SrvPrint0( "**** CALLING NTQUERYEAFILE\n" );
|
||
|
||
//
|
||
// Set up the proper get EA list if one was specified on input.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT(EaList) ) {
|
||
useEaList = (PFILE_GET_EA_INFORMATION)( (PCHAR)EaList +
|
||
EaInformation->GetEaListOffset );
|
||
EaListLength -= EaInformation->GetEaListOffset;
|
||
}
|
||
|
||
//
|
||
// Do the EA query operation using a directly-build IRP. Doing
|
||
// this rather than calling NtQueryEaFile eliminates a buffered I/O
|
||
// copy of the EAs and allows use of a kernel event object.
|
||
//
|
||
// The query is performed synchronously, which may be a
|
||
// detriment to performance. However, it may be the case that
|
||
// routines calling SrvQueryEaFile want to exploit the
|
||
// asynchronous capabilities of the IO system, so keeping this
|
||
// routine synchronous significantly simplifies their job.
|
||
//
|
||
|
||
status = SrvIssueQueryEaRequest(
|
||
FileHandle,
|
||
(PCHAR)EaInformation->Buffer,
|
||
EaInformation->BufferLength,
|
||
useEaList,
|
||
EaListLength,
|
||
IsFirstCall,
|
||
EaErrorOffset
|
||
);
|
||
|
||
//
|
||
// If there are no more EAs to be gotten, then stop.
|
||
//
|
||
|
||
if ( status == STATUS_NO_MORE_EAS ||
|
||
status == STATUS_NONEXISTENT_EA_ENTRY ||
|
||
status == STATUS_NO_EAS_ON_FILE ) {
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint0( "SrvQueryEaFile: No more EAs (or file has no EAs).\n" );
|
||
}
|
||
|
||
return STATUS_NO_MORE_EAS;
|
||
}
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
return status;
|
||
}
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
SrvPrint1( "NtQueryEaFile succeeded: %X\n", status );
|
||
}
|
||
|
||
//
|
||
// Set up the offset into the get EA list by counting how many
|
||
// full EAs were returned, then walking that far into the get
|
||
// EA list.
|
||
//
|
||
// If all the requested EAs were returned, set the offset to
|
||
// 0xFFFFFFFF so that we know to return STATUS_NO_MORE_EAS.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT(EaList) ) {
|
||
|
||
CLONG numberOfGetEas;
|
||
CLONG numberOfFullEas;
|
||
|
||
numberOfGetEas = SrvGetNumberOfEasInList( useEaList );
|
||
numberOfFullEas = SrvGetNumberOfEasInList( EaInformation->Buffer );
|
||
|
||
ASSERT( numberOfGetEas >= numberOfFullEas );
|
||
|
||
if ( numberOfGetEas == numberOfFullEas ) {
|
||
|
||
EaInformation->GetEaListOffset = 0xFFFFFFFF;
|
||
|
||
} else {
|
||
|
||
CLONG i;
|
||
|
||
//
|
||
// Walk the get EA list until we have passed the number
|
||
// of EAs that were returned. This assumes that we got
|
||
// back at least one EA--if not even one EA would fit in
|
||
// the buffer, SrvIssueQueryEaRequest should have
|
||
// returned STATUS_BUFFER_OVERFLOW.
|
||
//
|
||
|
||
for ( i = 0; i < numberOfFullEas; i++ ) {
|
||
useEaList = (PFILE_GET_EA_INFORMATION)(
|
||
(PCHAR)useEaList +
|
||
useEaList->NextEntryOffset );
|
||
}
|
||
|
||
EaInformation->GetEaListOffset = (ULONG)((PCHAR)useEaList -
|
||
(PCHAR)EaList);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Set up CurrentEntry pointer to point to the first entry in the
|
||
// buffer.
|
||
//
|
||
|
||
*currentEntry = (PFILE_FULL_EA_INFORMATION)EaInformation->Buffer;
|
||
|
||
IF_DEBUG(SEARCH) {
|
||
ANSI_STRING name;
|
||
name.Length = (*currentEntry)->EaNameLength;
|
||
name.Buffer = (*currentEntry)->EaName;
|
||
SrvPrint2( "First EA name is %z, length = %ld\n",
|
||
(PCSTRING)&name, (*currentEntry)->EaNameLength );
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvQueryEaFile
|
||
|
||
|
||
|
||
VOID
|
||
SrvTimeToDosTime (
|
||
IN PLARGE_INTEGER SystemTime,
|
||
OUT PSMB_DATE DosDate,
|
||
OUT PSMB_TIME DosTime
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function converts a time in NT format to the format used by
|
||
MS-DOS.
|
||
|
||
Arguments:
|
||
|
||
SystemTime - a pointer to an NT time to convert.
|
||
|
||
DosDate - a pointer to a location in which to store the date in DOS format.
|
||
|
||
DosTime - a pointer to a location in which to store the time in DOS format.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
TIME_FIELDS timeFields;
|
||
LARGE_INTEGER localTime;
|
||
|
||
PAGED_CODE( );
|
||
|
||
if ( SystemTime->QuadPart == 0 ) {
|
||
goto zerotime;
|
||
}
|
||
|
||
//
|
||
// Add almost two seconds to round up to the nearest double second.
|
||
// We need to do this to be compatible with the NT rdr and NT FAT
|
||
// filesystem.
|
||
//
|
||
|
||
SystemTime->QuadPart += AlmostTwoSeconds;
|
||
|
||
//
|
||
// Convert System time (UTC) to local NT time
|
||
//
|
||
|
||
ExSystemTimeToLocalTime( SystemTime, &localTime );
|
||
|
||
RtlTimeToTimeFields(
|
||
&localTime,
|
||
&timeFields
|
||
);
|
||
|
||
DosDate->Struct.Day = timeFields.Day;
|
||
DosDate->Struct.Month = timeFields.Month;
|
||
DosDate->Struct.Year = (SHORT)(timeFields.Year - 1980);
|
||
|
||
DosTime->Struct.TwoSeconds = (SHORT)(timeFields.Second / 2);
|
||
DosTime->Struct.Minutes = timeFields.Minute;
|
||
DosTime->Struct.Hours = timeFields.Hour;
|
||
|
||
return;
|
||
|
||
zerotime:
|
||
|
||
DosDate->Struct.Day = 0;
|
||
DosDate->Struct.Month = 0;
|
||
DosDate->Struct.Year = 0;
|
||
|
||
DosTime->Struct.TwoSeconds = 0;
|
||
DosTime->Struct.Minutes = 0;
|
||
DosTime->Struct.Hours = 0;
|
||
|
||
return;
|
||
} // SrvTimeToDosTime
|
||
|
||
|
||
VOID
|
||
SrvDosTimeToTime (
|
||
OUT PLARGE_INTEGER SystemTime,
|
||
IN SMB_DATE DosDate,
|
||
IN SMB_TIME DosTime
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function converts a time in NT format to the format used by
|
||
MS-DOS.
|
||
|
||
Arguments:
|
||
|
||
Time - a pointer to a location in which to store the NT time.
|
||
|
||
DosDate - a pointer the date in DOS format.
|
||
|
||
DosDate - a pointer the date in DOS format.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
TIME_FIELDS timeFields;
|
||
LARGE_INTEGER localTime;
|
||
|
||
PAGED_CODE( );
|
||
|
||
timeFields.Day = DosDate.Struct.Day;
|
||
timeFields.Month = DosDate.Struct.Month;
|
||
timeFields.Year = (SHORT)(DosDate.Struct.Year + 1980);
|
||
|
||
timeFields.Milliseconds = 0;
|
||
timeFields.Second = (SHORT)(DosTime.Struct.TwoSeconds * 2);
|
||
timeFields.Minute = DosTime.Struct.Minutes;
|
||
timeFields.Hour = DosTime.Struct.Hours;
|
||
|
||
if ( !RtlTimeFieldsToTime( &timeFields, &localTime ) ) {
|
||
goto zerotime;
|
||
}
|
||
|
||
ExLocalTimeToSystemTime( &localTime, SystemTime );
|
||
return;
|
||
|
||
zerotime:
|
||
|
||
SystemTime->QuadPart = 0;
|
||
return;
|
||
|
||
} // SrvDosTimeToTime
|
||
|
||
|
||
USHORT
|
||
SrvGetOs2TimeZone(
|
||
IN PLARGE_INTEGER SystemTime
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function gets the timezone bias.
|
||
|
||
Arguments:
|
||
|
||
SystemTime - The current UTC time expressed.
|
||
|
||
Return Value:
|
||
|
||
The time zone bias in minutes from GMT.
|
||
|
||
--*/
|
||
|
||
{
|
||
LARGE_INTEGER zeroTime;
|
||
LARGE_INTEGER timeZoneBias;
|
||
|
||
PAGED_CODE( );
|
||
|
||
zeroTime.QuadPart = 0;
|
||
|
||
//
|
||
// Specifying a zero local time will give you the time zone bias.
|
||
//
|
||
|
||
ExLocalTimeToSystemTime( &zeroTime, &timeZoneBias );
|
||
|
||
//
|
||
// Convert the bias unit from 100ns to minutes. The maximum value
|
||
// for the bias is 720 minutes so a USHORT is big enough to contain
|
||
// it.
|
||
//
|
||
|
||
return (SHORT)(timeZoneBias.QuadPart / (10*1000*1000*60));
|
||
|
||
} // SrvGetOs2TimeZone
|
||
|
||
NTSTATUS
|
||
SrvQueryBasicAndStandardInformation(
|
||
HANDLE FileHandle,
|
||
PFILE_OBJECT FileObject OPTIONAL,
|
||
PFILE_BASIC_INFORMATION FileBasicInfo,
|
||
PFILE_STANDARD_INFORMATION FileStandardInfo OPTIONAL
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
PFILE_OBJECT fileObject;
|
||
PDEVICE_OBJECT deviceObject;
|
||
PFAST_IO_DISPATCH fastIoDispatch;
|
||
PFAST_IO_QUERY_BASIC_INFO fastQueryBasicInfo;
|
||
PFAST_IO_QUERY_STANDARD_INFO fastQueryStandardInfo;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ASSERT( FileBasicInfo != NULL );
|
||
|
||
//
|
||
// Get a pointer to the file object, so that we can directly
|
||
// access the fast IO routines, if they exists.
|
||
//
|
||
|
||
if ( !ARGUMENT_PRESENT( FileObject ) ) {
|
||
|
||
status = ObReferenceObjectByHandle(
|
||
FileHandle,
|
||
0,
|
||
NULL,
|
||
KernelMode,
|
||
(PVOID *)&fileObject,
|
||
NULL
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
SrvLogServiceFailure( SRV_SVC_OB_REF_BY_HANDLE, status );
|
||
|
||
//
|
||
// This internal error bugchecks the system.
|
||
//
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_IMPOSSIBLE,
|
||
"CompleteOpen: unable to reference file handle 0x%lx",
|
||
FileHandle,
|
||
NULL
|
||
);
|
||
|
||
return(status);
|
||
|
||
}
|
||
|
||
} else {
|
||
fileObject = FileObject;
|
||
}
|
||
|
||
deviceObject = IoGetRelatedDeviceObject( fileObject );
|
||
fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
|
||
|
||
if ( fastIoDispatch ) {
|
||
fastQueryBasicInfo = fastIoDispatch->FastIoQueryBasicInfo;
|
||
fastQueryStandardInfo = fastIoDispatch->FastIoQueryStandardInfo;
|
||
} else {
|
||
fastQueryBasicInfo = NULL;
|
||
fastQueryStandardInfo = NULL;
|
||
}
|
||
|
||
if ( fastQueryBasicInfo &&
|
||
fastQueryBasicInfo(
|
||
fileObject,
|
||
TRUE,
|
||
FileBasicInfo,
|
||
&ioStatus,
|
||
deviceObject
|
||
) ) {
|
||
|
||
status = ioStatus.Status;
|
||
|
||
} else {
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatus,
|
||
(PVOID)FileBasicInfo,
|
||
sizeof(FILE_BASIC_INFORMATION),
|
||
FileBasicInformation
|
||
);
|
||
}
|
||
|
||
//
|
||
// If we're done if there was a failure, return
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( FileStandardInfo ) && NT_SUCCESS(status) ) {
|
||
|
||
//
|
||
// Get the standard info
|
||
//
|
||
|
||
if ( fastQueryStandardInfo &&
|
||
fastQueryStandardInfo(
|
||
fileObject,
|
||
TRUE,
|
||
FileStandardInfo,
|
||
&ioStatus,
|
||
deviceObject
|
||
) ) {
|
||
|
||
status = ioStatus.Status;
|
||
|
||
} else {
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatus,
|
||
(PVOID)FileStandardInfo,
|
||
sizeof(FILE_STANDARD_INFORMATION),
|
||
FileStandardInformation
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( !ARGUMENT_PRESENT( FileObject ) ) {
|
||
ObDereferenceObject( fileObject );
|
||
}
|
||
return(status);
|
||
|
||
} // SrvQueryBasicAndStandardInformation
|
||
|
||
NTSTATUS
|
||
SrvQueryNetworkOpenInformation(
|
||
HANDLE FileHandle,
|
||
PFILE_OBJECT FileObject OPTIONAL,
|
||
PSRV_NETWORK_OPEN_INFORMATION SrvNetworkOpenInformation,
|
||
BOOLEAN QueryEaSize
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
PFILE_OBJECT fileObject;
|
||
PDEVICE_OBJECT deviceObject;
|
||
PFAST_IO_DISPATCH fastIoDispatch;
|
||
PFAST_IO_QUERY_NETWORK_OPEN_INFO fastQueryNetworkOpenInfo;
|
||
FILE_BASIC_INFORMATION FileBasicInfo;
|
||
FILE_STANDARD_INFORMATION FileStandardInfo;
|
||
IO_STATUS_BLOCK ioStatus;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Get a pointer to the file object, so that we can directly
|
||
// access the fast IO routines, if they exist.
|
||
//
|
||
if ( !ARGUMENT_PRESENT( FileObject ) ) {
|
||
|
||
status = ObReferenceObjectByHandle(
|
||
FileHandle,
|
||
0,
|
||
NULL,
|
||
KernelMode,
|
||
(PVOID *)&fileObject,
|
||
NULL
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
SrvLogServiceFailure( SRV_SVC_OB_REF_BY_HANDLE, status );
|
||
|
||
//
|
||
// This internal error bugchecks the system.
|
||
//
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_IMPOSSIBLE,
|
||
"CompleteOpen: unable to reference file handle 0x%lx",
|
||
FileHandle,
|
||
NULL
|
||
);
|
||
|
||
return(status);
|
||
|
||
}
|
||
|
||
} else {
|
||
fileObject = FileObject;
|
||
}
|
||
|
||
deviceObject = IoGetRelatedDeviceObject( fileObject );
|
||
fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
|
||
|
||
if( !QueryEaSize &&
|
||
fastIoDispatch &&
|
||
fastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET(FAST_IO_DISPATCH,FastIoQueryNetworkOpenInfo)) {
|
||
|
||
fastQueryNetworkOpenInfo = fastIoDispatch->FastIoQueryNetworkOpenInfo;
|
||
|
||
if( fastQueryNetworkOpenInfo &&
|
||
|
||
fastQueryNetworkOpenInfo(
|
||
fileObject,
|
||
TRUE,
|
||
(PFILE_NETWORK_OPEN_INFORMATION)SrvNetworkOpenInformation,
|
||
&ioStatus,
|
||
deviceObject ) ) {
|
||
|
||
status = ioStatus.Status;
|
||
|
||
if ( !ARGUMENT_PRESENT( FileObject ) ) {
|
||
ObDereferenceObject( fileObject );
|
||
}
|
||
|
||
return status;
|
||
}
|
||
}
|
||
|
||
//
|
||
// The fast path didn't work. Do it the slow way
|
||
//
|
||
status = SrvQueryBasicAndStandardInformation(
|
||
FileHandle,
|
||
fileObject,
|
||
&FileBasicInfo,
|
||
&FileStandardInfo
|
||
);
|
||
|
||
if ( !ARGUMENT_PRESENT( FileObject ) ) {
|
||
ObDereferenceObject( fileObject );
|
||
}
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
return status;
|
||
}
|
||
|
||
SrvNetworkOpenInformation->CreationTime = FileBasicInfo.CreationTime;
|
||
SrvNetworkOpenInformation->LastAccessTime = FileBasicInfo.LastAccessTime;
|
||
SrvNetworkOpenInformation->LastWriteTime = FileBasicInfo.LastWriteTime;
|
||
SrvNetworkOpenInformation->ChangeTime = FileBasicInfo.ChangeTime;
|
||
SrvNetworkOpenInformation->AllocationSize = FileStandardInfo.AllocationSize;
|
||
SrvNetworkOpenInformation->EndOfFile = FileStandardInfo.EndOfFile;
|
||
SrvNetworkOpenInformation->FileAttributes = FileBasicInfo.FileAttributes;
|
||
|
||
if ( QueryEaSize ) {
|
||
|
||
FILE_EA_INFORMATION fileEaInformation;
|
||
|
||
status = NtQueryInformationFile(
|
||
FileHandle,
|
||
&ioStatus,
|
||
(PVOID)&fileEaInformation,
|
||
sizeof(FILE_EA_INFORMATION),
|
||
FileEaInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvQueryInformationFile: NtQueryInformationFile "
|
||
"(EA information) failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
SrvNetworkOpenInformation->EaSize = fileEaInformation.EaSize;
|
||
}
|
||
|
||
return(status);
|
||
|
||
} // SrvQueryNetworkOpenInformation
|