|
|
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
oplock.c
Abstract:
This module contains the SPUDCreateFile service.
Author:
John Ballard (jballard) 13-Dec-1996
Revision History:
Keith Moore (keithmo) 02-Feb-1998 Made it work, added much needed comments.
--*/
#include "spudp.h"
//
// Private prototypes.
//
NTSTATUS SpudpOplockCompletion( PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context );
#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, SPUDCreateFile )
#endif
#if 0
NOT PAGEABLE -- SpudpOplockCompletion #endif
//
// Public routines.
//
NTSTATUS SPUDCreateFile( OUT PHANDLE FileHandle, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateOptions, IN SECURITY_INFORMATION SecurityInformation, OUT PSECURITY_DESCRIPTOR SecDescBuffer, IN ULONG SecDescLength, OUT PULONG SecDescLengthNeeded, IN PVOID OplockContext, IN LARGE_INTEGER OplockMaxFileSize, OUT PBOOLEAN OplockGranted, OUT PSPUD_FILE_INFORMATION FileInfo )
/*++
Routine Description:
This service opens a file and queries information about the file. This service can also optionally retrieve any security descriptor associated with the file and issue an oplock request.
Arguments:
FileHandle - A pointer to a variable to receive the handle to the open file.
ObjectAttributes - Supplies the attributes to be used for file object (name, SECURITY_DESCRIPTOR, etc.)
IoStatusBlock - Specifies the address of the caller's I/O status block.
FileAttributes - Specifies the attributes that should be set on the file, if it is created.
ShareAccess - Supplies the types of share access that the caller would like to the file.
CreateOptions - Caller options for how to perform the create/open.
SecurityInformation - Indicates the type of security information to retrieve.
SecDescBuffer - Supplies a buffer to receive the file's security descriptor.
SecDescLength - Supplies the length of the security descriptor buffer.
SecDescLengthNeeded - Receives the length needed to store the security descriptor.
OplockContext - Supplies an uninterpreted context used during oplock break notifications. If this value is NULL, then no oplock request is issued.
OplockMaxFileSize = If the size of the file opened is larger than OplockMaxFileSize then no oplock request is issued.
OplockGranted - A pointer to a variable to receive the status of the oplock request. This parameter is ignored if OplockContext is NULL.
FileInfo - Supplies a buffer to receive information about the file.
Return Value:
NTSTATUS - Completion status.
--*/
{
NTSTATUS status; PFILE_OBJECT fileObject; PIRP irp; PIO_STACK_LOCATION irpSp; PDEVICE_OBJECT deviceObject; HANDLE localFileHandle; IO_STATUS_BLOCK localIoStatusBlock; FILE_BASIC_INFORMATION basicInfo; FILE_STANDARD_INFORMATION standardInfo; PVOID completionPort;
//
// Sanity check.
//
PAGED_CODE();
ASSERT( SPUD_OPLOCK_BREAK_OPEN == FILE_OPLOCK_BROKEN_TO_LEVEL_2 ); ASSERT( SPUD_OPLOCK_BREAK_CLOSE == FILE_OPLOCK_BROKEN_TO_NONE );
status = SPUD_ENTER_SERVICE( "SPUDCreateFile", TRUE );
if( !NT_SUCCESS(status) ) { return status; }
//
// SPUD doesn't support kernel-mode callers. In fact, we don't
// even build the "system stubs" necessary to invoke SPUD from
// kernel-mode.
//
ASSERT( ExGetPreviousMode() == UserMode );
//
// Probe the generic arguments. We don't need to probe the FileHandle
// and IoStatusBlock parameters, as they will be probed by IoCreateFile().
//
try {
//
// The FileInfo parameter must be writeable by the caller.
//
ProbeForWrite( FileInfo, sizeof(*FileInfo), sizeof(ULONG) );
} except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// Open the file. If successful, this will write the newly-opened
// file handle into *FileHandle.
//
status = IoCreateFile( FileHandle, // FileHandle
GENERIC_READ // DesiredAccess
| SYNCHRONIZE //
| FILE_READ_ATTRIBUTES, //
ObjectAttributes, // ObjectAttributes
IoStatusBlock, // IoStatusBlock
NULL, // AllocationSize
FileAttributes, // FileAttributes
ShareAccess, // ShareAccess
FILE_OPEN, // Disposition
CreateOptions, // CreateOptions
NULL, // EaBuffer
0, // EaLength
CreateFileTypeNone, // CreateFileType
NULL, // ExtraCreateParameters
0 // Options
);
if( !NT_SUCCESS(status) ) { SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// Snag the handle from the user-mode buffer.
//
try { localFileHandle = *FileHandle; } except( EXCEPTION_EXECUTE_HANDLER ) { //
// We faulted trying to read the file handle from user-mode memory.
// The user-mode code must have mucked with the virtual address
// space after we called IoCreateFile(). Since we cannot get the
// file handle, we cannot close the file, and the user-mode code is
// going to leak the handle.
//
status = GetExceptionCode(); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// Query the file attributes.
//
status = ZwQueryInformationFile( localFileHandle, // FileHandle
&localIoStatusBlock, // IoStatusBlock
&basicInfo, // FileInformation
sizeof(basicInfo), // Length
FileBasicInformation // FileInformationClass
);
if( NT_SUCCESS(status) ) { status = ZwQueryInformationFile( localFileHandle, // FileHandle
&localIoStatusBlock, // IoStatusBlock
&standardInfo, // FileInformation
sizeof(standardInfo), // Length
FileStandardInformation // FileInformationClass
); }
if( NT_SUCCESS(status) ) { //
// Copy the file attributes to the user-mode buffer.
//
try { RtlCopyMemory( &FileInfo->BasicInformation, &basicInfo, sizeof(basicInfo) );
RtlCopyMemory( &FileInfo->StandardInformation, &standardInfo, sizeof(standardInfo) ); } except( EXCEPTION_EXECUTE_HANDLER ) { status = GetExceptionCode(); } }
//
// If we failed for any reason (either we failed to query the attributes
// or we faulted trying to copy them to user-mode) then close the file
// handle and bail.
//
if( !NT_SUCCESS(status) ) { NtClose( localFileHandle ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// If the caller passed in a security descriptor buffer, then try to
// query the security descriptor.
//
if( SecDescLength > 0 ) {
//
// Query the security descriptor from the file. Note that
// since the previous mode == UserMode, we don't need to
// probe SecDescBuffer or SecDescLengthNeeded (they will be
// probed by the NtQuerySecurityObject() API).
//
status = NtQuerySecurityObject( localFileHandle, // Handle
SecurityInformation, // SecurityInformation
SecDescBuffer, // SecurityDescriptor
SecDescLength, // Length
SecDescLengthNeeded // LengthNeeded
);
if( !NT_SUCCESS(status) ) {
if( status == STATUS_NOT_SUPPORTED ) { //
// This status code is returned for filesystems that don't
// support security. We'll just fake an empty descriptor.
//
try { *SecDescLengthNeeded = 0; status = STATUS_SUCCESS; } except( EXCEPTION_EXECUTE_HANDLER ) { status = GetExceptionCode(); } } else if( status == STATUS_BUFFER_TOO_SMALL ) { //
// Mapping STATUS_BUFFER_TOO_SMALL to STATUS_SUCCESS seems
// a bit bizarre. The intent here is to succeed the
// SPUDCreateFile() call. The fact that *SecDescLengthNeeded
// is returned with a value larger than SecDescLength is the
// user-mode code's signal that it should allocate a new
// buffer & retrieve the security descriptor "out-of-band".
//
#if DBG
try { ASSERT( *SecDescLengthNeeded > SecDescLength ); } except( EXCEPTION_EXECUTE_HANDLER ) { NOTHING; } #endif
status = STATUS_SUCCESS; }
if( !NT_SUCCESS(status) ) { NtClose( localFileHandle ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
}
}
//
// If OplockContext == NULL then the caller is not interested in
// oplocks, so we can just return successfully right now.
//
if( OplockContext == NULL ) { ASSERT( status == STATUS_SUCCESS ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// Probe the oplock-specific parameters.
//
try {
//
// The OplockGranted parameter must be writeable. Set it to
// FALSE until proven otherwise.
//
ProbeAndWriteBoolean( OplockGranted, FALSE );
} except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); NtClose( localFileHandle ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// If the entity just opened is actually a directory (rather than
// a "normal" file) then there's no point in trying to acquire the
// oplock.
//
// Note that this check must be after the OplockGranted parameter
// is probed so that we know it is set to FALSE.
//
if( standardInfo.Directory ) { ASSERT( status == STATUS_SUCCESS ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// If the file is smaller than the size specified in the
// OplockMaxFileSize parameter, then the user doesn't want
// an oplock.
//
if ( standardInfo.EndOfFile.QuadPart > OplockMaxFileSize.QuadPart ) { ASSERT( status == STATUS_SUCCESS ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// See if we can acquire the completion port.
//
completionPort = SpudReferenceCompletionPort();
if( completionPort == NULL ) { status = STATUS_INVALID_DEVICE_REQUEST; SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status; }
//
// Reference the file handle
//
status = ObReferenceObjectByHandle( localFileHandle, // Handle
0L, // DesiredAccess
*IoFileObjectType, // ObjectType
UserMode, // AccessMode
(PVOID *)&fileObject, // Object
NULL // HandleInformation
);
if( !NT_SUCCESS(status) ) { NtClose( localFileHandle ); SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, TRUE ); return status; }
TRACE_OB_REFERENCE( fileObject );
//
// Chase down the device object associated with this file object.
//
deviceObject = IoGetRelatedDeviceObject( fileObject );
//
// Allocate and initialize the IRP.
//
irp = IoAllocateIrp( deviceObject->StackSize, FALSE );
if( !irp ) { TRACE_OB_DEREFERENCE( fileObject ); ObDereferenceObject( fileObject ); NtClose( localFileHandle ); status = STATUS_INSUFFICIENT_RESOURCES; SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, TRUE ); return status; }
irp->RequestorMode = UserMode; irp->Tail.Overlay.OriginalFileObject = fileObject; irp->Tail.Overlay.Thread = PsGetCurrentThread();
irpSp = IoGetNextIrpStackLocation( irp );
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL; irpSp->FileObject = fileObject; irpSp->DeviceObject = deviceObject;
// irpSp->Parameters.FileSystemControl.OutputBufferLength = 0;
// irpSp->Parameters.FileSystemControl.InputBufferLength = 0;
irpSp->Parameters.FileSystemControl.FsControlCode = FSCTL_REQUEST_BATCH_OPLOCK;
IoSetCompletionRoutine( irp, // Irp
SpudpOplockCompletion, // CompletionRoutine
OplockContext, // Context
TRUE, // InvokeOnSuccess
TRUE, // InvokeOnError
TRUE // InvokeOnCancel
);
//
// Issue the IRP to the file system.
//
status = IoCallDriver( deviceObject, irp );
if( NT_SUCCESS(status) ) { //
// The oplock IRP was successfully issued to the file system. We
// can now assume the IRP will complete later, therefore we will
// set the user's OplockGranted flag.
//
try { *OplockGranted = TRUE; } except( EXCEPTION_EXECUTE_HANDLER ) { //
// Grr...
//
// This is a sticky situation. We've already opened the file
// and successfully acquired the oplock. Closing the file handle
// here would probably confuse the user-mode code, as it would
// see the oplock break after an unsuccessful SPUDCreateFile().
//
// The only thing we can do here is just drop the failure on the
// floor. This is not as bad as it seems, as the user-mode code
// will presumably fault when it tries to access OplockGranted.
//
} }
//
// Regardless of the completion status of the oplock IRP, return
// STATUS_SUCCESS to the caller. Failure to acquire the oplock is
// insufficient grounds to fail the SPUDCreateFile() call.
//
status = STATUS_SUCCESS; SPUD_LEAVE_SERVICE( "SPUDCreateFile", status, FALSE ); return status;
} // SPUDCreateFile
//
// Private routines.
//
NTSTATUS SpudpOplockCompletion( PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context )
/*++
Routine Description:
Completion routine for oplock IRPs.
Arguments:
DeviceObject - The device object completing the request (unused).
Irp - The IRP being completed.
Context - The context associated with the request. This is actually the user's OplockContext passed into SPUDCreateFile().
Return Value:
NTSTATUS - Completion status.
--*/
{
PFILE_OBJECT fileObject;
//
// Dereference the file object since we're done with it.
//
fileObject = Irp->Tail.Overlay.OriginalFileObject; TRACE_OB_DEREFERENCE( fileObject ); ObDereferenceObject( fileObject );
if( NT_SUCCESS(Irp->IoStatus.Status) ) {
//
// Sanity check.
//
ASSERT( Irp->IoStatus.Information == SPUD_OPLOCK_BREAK_OPEN || Irp->IoStatus.Information == SPUD_OPLOCK_BREAK_CLOSE );
//
// Post the IRP to the completion port. The completion key must be
// the OplockContext passed into SPUDCreateFile().
//
// Note that NULL is used as a distinguished value in UserApcContext.
// This is an indicator to AtqpProcessContext() that an I/O
// completion is actually an oplock break.
//
Irp->Tail.CompletionKey = Context; Irp->Overlay.AsynchronousParameters.UserApcContext = NULL;
//
// Hack-O-Rama. This is absolutely required to get the I/O completion
// port stuff to work. It turns out that the CurrentStackLocation
// field overlays the PacketType field. Since PacketType must be set
// to IopCompletionPacketIrp (which just happens to be zero), we'll
// set CurrentStackLocation to NULL. Ugly. We should really make this
// part of the kernel. Maybe something like this:
//
// VOID
// IoSetIrpCompletion(
// IN PVOID IoCompletion,
// IN PVOID KeyContext,
// IN PVOID ApcContext,
// IN PIRP Irp
// );
//
// We could then replace the following lines (and a few lines above)
// with:
//
// IoSetIrpCompletion(
// SpudCompletionPort,
// Context,
// NULL,
// Irp
// );
//
Irp->Tail.Overlay.CurrentStackLocation = NULL;
KeInsertQueue( (PKQUEUE)SpudCompletionPort, &Irp->Tail.Overlay.ListEntry );
} else {
//
// The oplock IRP failed. We'll just drop this IRP on the floor and
// free it. Since we already notified the caller (through the
// OplockGranted parameter to SPUDCreateFile) that the oplock could
// not be acquired, we don't want to notify them again through the
// completion port.
//
// Also note that pending oplock IRPs are not cancelled in the
// "normal" sense (i.e. with STATUS_CANCELLED) when the file handle
// is closed. Rather, they are completed *successfully* with the
// FILE_OPLOCK_BROKEN_TO_NONE (SPUD_OPLOCK_BREAK_CLOSE) completion
// code. Ergo, cancelled oplock IRPs should never go through this
// code path.
//
IoFreeIrp( Irp );
}
//
// We're done with the completion port. Remove the reference we added
// in SPUDCreateFile().
//
SpudDereferenceCompletionPort();
//
// Tell IO to stop processing this IRP. The IRP will be freed in
// NtRemoveIoCompletion (the kernel-mode worker for the
// GetQueuedCompletionStatus() API).
//
return STATUS_MORE_PROCESSING_REQUIRED;
} // SpudpOplockCompletion
|