671 lines
19 KiB
C
671 lines
19 KiB
C
/*++
|
||
|
||
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
|
||
|