/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    qsinfo.c

Abstract:

    This module contains the code to implement the NtQueryInformationFile and
    NtSetInformationFile system services for the NT I/O system.

--*/

#include "iomgr.h"

//
// Create local definitions for long flag names to make code slightly more
// readable.
//

#define FSIO_A  FILE_SYNCHRONOUS_IO_ALERT
#define FSIO_NA FILE_SYNCHRONOUS_IO_NONALERT

//
// Forward declarations of local routines.
//

ULONG
IopGetModeInformation(
    IN PFILE_OBJECT FileObject
    );

#pragma alloc_text(PAGE, IopGetModeInformation)
#pragma alloc_text(PAGE, NtQueryInformationFile)
#pragma alloc_text(PAGE, NtSetInformationFile)

ULONG
IopGetModeInformation(
    IN PFILE_OBJECT FileObject
    )

/*++

Routine Description:

    This encapsulates extracting and translating the mode bits from
    the passed file object, to be returned from a query information call.

Arguments:

    FileObject - Specifies the file object for which to return Mode info.

Return Value:

    The translated mode information is returned.

--*/

{
    ULONG mode = 0;

    if (FileObject->Flags & FO_WRITE_THROUGH) {
        mode = FILE_WRITE_THROUGH;
    }
    if (FileObject->Flags & FO_SEQUENTIAL_ONLY) {
        mode |= FILE_SEQUENTIAL_ONLY;
    }
    if (FileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING) {
        mode |= FILE_NO_INTERMEDIATE_BUFFERING;
    }
    if (FileObject->Flags & FO_SYNCHRONOUS_IO) {
        if (FileObject->Flags & FO_ALERTABLE_IO) {
            mode |= FILE_SYNCHRONOUS_IO_ALERT;
        } else {
            mode |= FILE_SYNCHRONOUS_IO_NONALERT;
        }
    }
    if (FileObject->Flags & FO_DELETE_ON_CLOSE) {
        mode |= FILE_DELETE_ON_CLOSE;
    }
    return mode;
}

NTSTATUS
NtQueryInformationFile (
    __in HANDLE FileHandle,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __out_bcount(Length) PVOID FileInformation,
    __in ULONG Length,
    __in FILE_INFORMATION_CLASS FileInformationClass
    )

/*++

Routine Description:

    This service returns the requested information about a specified file.
    The information returned is determined by the FileInformationClass that
    is specified, and it is placed into the caller's FileInformation buffer.

Arguments:

    FileHandle - Supplies a handle to the file about which the requested
        information should be returned.

    IoStatusBlock - Address of the caller's I/O status block.

    FileInformation - Supplies a buffer to receive the requested information
        returned about the file.

    Length - Supplies the length, in bytes, of the FileInformation buffer.

    FileInformationClass - Specifies the type of information which should be
        returned about the file.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PFAST_IO_DISPATCH fastIoDispatch;
    PKEVENT event = (PKEVENT) NULL;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    IO_STATUS_BLOCK localIoStatus = {0};
    OBJECT_HANDLE_INFORMATION handleInformation;
    BOOLEAN synchronousIo;
    BOOLEAN skipDriver;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    if (requestorMode != KernelMode) {

        //
        // Ensure that the FileInformationClass parameter is legal for querying
        // information about the file.
        //

        if ((ULONG) FileInformationClass >= FileMaximumInformation ||
            !IopQueryOperationLength[FileInformationClass]) {
            return STATUS_INVALID_INFO_CLASS;
        }

        //
        // Ensure that the supplied buffer is large enough to contain the
        // information associated with the specified set operation that is
        // to be performed.
        //

        if (Length < (ULONG) IopQueryOperationLength[FileInformationClass]) {
            return STATUS_INFO_LENGTH_MISMATCH;
        }

        //
        // The caller's access mode is not kernel so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus( IoStatusBlock );

            //
            // The FileInformation buffer must be writeable by the caller.
            //

#if defined(_X86_)
            ProbeForWrite( FileInformation, Length, sizeof( ULONG ) );
#elif defined(_WIN64)

            //
            // If we are a wow64 process, follow the X86 rules
            //

            if (PsGetCurrentProcessByThread(CurrentThread)->Wow64Process) {
                ProbeForWrite( FileInformation, Length, sizeof( ULONG ) );
            } else {
                ProbeForWrite( FileInformation,
                               Length,
                               IopQuerySetAlignmentRequirement[FileInformationClass] );
            }
#else
            ProbeForWrite( FileInformation,
                           Length,
                           IopQuerySetAlignmentRequirement[FileInformationClass] );
#endif

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while probing the caller's
            // parameters.  Simply return an appropriate error status
            // code.
            //


            return GetExceptionCode();
        }

#if DBG

    } else {

        //
        // The caller's mode is kernel.  Ensure that at least the information
        // class and lengths are appropriate.
        //

        if ((ULONG) FileInformationClass >= FileMaximumInformation ||
            !IopQueryOperationLength[FileInformationClass]) {
            return STATUS_INVALID_INFO_CLASS;
        }

        if (Length < (ULONG) IopQueryOperationLength[FileInformationClass]) {
            return STATUS_INFO_LENGTH_MISMATCH;
        }

#endif // DBG

    }

    //
    // There were no blatant errors so far, so reference the file object so
    // the target device object can be found.  Note that if the handle does
    // not refer to a file object, or if the caller does not have the required
    // access to the file, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        IopQueryOperationAccess[FileInformationClass],
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        &handleInformation);

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

    //
    // Get the address of the target device object.  If this file represents
    // a device that was opened directly, then simply use the device or its
    // attached device(s) directly.  Also get the address of the Fast Io
    // dispatch structure.
    //

    if (!(fileObject->Flags & FO_DIRECT_DEVICE_OPEN)) {
        deviceObject = IoGetRelatedDeviceObject( fileObject );
    } else {
        deviceObject = IoGetAttachedDevice( fileObject->DeviceObject );
    }
    fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;

    //
    // Make a special check here to determine whether this is a synchronous
    // I/O operation.  If it is, then wait here until the file is owned by
    // the current thread.  If this is not a (serialized) synchronous I/O
    // operation, then allocate and initialize the local event.
    //

    if (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                ObDereferenceObject( fileObject );
                return status;
            }
        }

        //
        // Make a special check here to determine whether or not the caller
        // is attempting to query the file position pointer.  If so, then
        // return it immediately and get out.
        //

        if (FileInformationClass == FilePositionInformation) {

            //
            // The caller has requested the current file position context
            // information.  This is a relatively frequent call, so it is
            // optimized here to cut through the normal IRP path.
            //
            // Begin by establishing a condition handler and attempting to
            // return both the file position information as well as the I/O
            // status block.  If writing the output buffer fails, then return
            // an appropriate error status code.  If writing the I/O status
            // block fails, then ignore the error.  This is what would
            // normally happen were everything to go through normal special
            // kernel APC processing.
            //

            BOOLEAN writingBuffer = TRUE;
            PFILE_POSITION_INFORMATION fileInformation = FileInformation;

            try {

                //
                // Return the current position information.
                //

                fileInformation->CurrentByteOffset = fileObject->CurrentByteOffset;
                writingBuffer = FALSE;

                //
                // Write the I/O status block.
                //

                IoStatusBlock->Status = STATUS_SUCCESS;
                IoStatusBlock->Information = sizeof( FILE_POSITION_INFORMATION );

            } except( EXCEPTION_EXECUTE_HANDLER ) {

                //
                // One of writing the caller's buffer or writing the I/O
                // status block failed.  Set the final status appropriately.
                //

                if (writingBuffer) {
                    status = GetExceptionCode();
                }

            }

            //
            // Note that the state of the event in the file object has not yet
            // been reset, so it need not be set either.  Therefore, simply
            // cleanup and return.
            //

            IopReleaseFileObjectLock( fileObject );
            ObDereferenceObject( fileObject );
            return status;

        //
        // Also do a special check if the caller it doing a query for basic or
        // standard information and if so then try the fast query calls if they
        // exist.
        //

        } else if (fastIoDispatch &&
                   (((FileInformationClass == FileBasicInformation) &&
                     fastIoDispatch->FastIoQueryBasicInfo) ||
                    ((FileInformationClass == FileStandardInformation) &&
                     fastIoDispatch->FastIoQueryStandardInfo))) {

            IO_STATUS_BLOCK localIoStatus;
            BOOLEAN queryResult = FALSE;
            BOOLEAN writingStatus = FALSE;

            //
            // Do the query and setting of the IoStatusBlock inside an exception
            // handler.  Note that if an exception occurs, other than writing
            // the status back, then the IRP route will be taken.  If an error
            // occurs attempting to write the status back to the caller's buffer
            // then it will be ignored, just as it would be on the long path.
            //

            try {

                if (FileInformationClass == FileBasicInformation) {
                    queryResult = fastIoDispatch->FastIoQueryBasicInfo( fileObject,
                                                                        TRUE,
                                                                        FileInformation,
                                                                        &localIoStatus,
                                                                        deviceObject );
                } else {
                    queryResult = fastIoDispatch->FastIoQueryStandardInfo( fileObject,
                                                                           TRUE,
                                                                           FileInformation,
                                                                           &localIoStatus,
                                                                           deviceObject );
                }

                if (queryResult) {
                    status = localIoStatus.Status;
                    writingStatus = TRUE;
                    *IoStatusBlock = localIoStatus;
                }

            } except( EXCEPTION_EXECUTE_HANDLER ) {

                //
                // If the result of the preceding block is an exception that
                // occurred after the Fast I/O path itself, then the query
                // actually succeeded so everything is done already, but the
                // user's I/O status buffer is not writable.  This case is
                // ignored to be consistent w/the long path.
                //

                if (!writingStatus) {
                    status = GetExceptionCode();
                }
            }

            //
            // If the results of the preceding statement block is true, then
            // the fast query call succeeded, so simply cleanup and return.
            //

            if (queryResult) {

                //
                // Note that once again, the event in the file object has not
                // yet been set reset, so it need not be set to the Signaled
                // state, so simply cleanup and return.
                //

                IopReleaseFileObjectLock( fileObject );
                ObDereferenceObject( fileObject );
                return status;
            }
        }
        synchronousIo = TRUE;
    } else {

        //
        // This is a synchronous API being invoked for a file that is opened
        // for asynchronous I/O.  This means that this system service is
        // to synchronize the completion of the operation before returning
        // to the caller.  A local event is used to do this.
        //

        event = ExAllocatePool( NonPagedPool, sizeof( KEVENT ) );
        if (event == NULL) {
            ObDereferenceObject( fileObject );
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        KeInitializeEvent( event, SynchronizationEvent, FALSE );
        synchronousIo = FALSE;
    }

    //
    // Set the file object to the Not-Signaled state.
    //

    KeClearEvent( &fileObject->Event );

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.
    //

    irp = IoAllocateIrp( deviceObject->StackSize, FALSE );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        if (!(fileObject->Flags & FO_SYNCHRONOUS_IO)) {
            ExFreePool( event );
        }

        IopAllocateIrpCleanup( fileObject, (PKEVENT) NULL );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->RequestorMode = requestorMode;

    //
    // Fill in the service independent parameters in the IRP.
    //

    if (synchronousIo) {
        irp->UserEvent = (PKEVENT) NULL;
        irp->UserIosb = IoStatusBlock;
    } else {
        irp->UserEvent = event;
        irp->UserIosb = &localIoStatus;
        irp->Flags = IRP_SYNCHRONOUS_API;
    }
    irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE) NULL;

    //
    // Get a pointer to the stack location for the first driver.  This will be
    // used to pass the original function codes and parameters.
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
    irpSp->FileObject = fileObject;

    //
    // Allocate a buffer which should be used to put the information into by
    // the driver.  This will be copied back to the caller's buffer when the
    // service completes.  This is done by setting the flag which says that
    // this is an input operation.
    //

    irp->UserBuffer = FileInformation;
    irp->AssociatedIrp.SystemBuffer = (PVOID) NULL;
    irp->MdlAddress = (PMDL) NULL;

    try {

        //
        // Allocate the system buffer using an exception handler so that
        // errors can be caught and handled.
        //

        irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithQuota( NonPagedPool,
                                                                   Length );
    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred by attempting to allocate the intermediary
        // system buffer.  Cleanup everything and return an appropriate error
        // status code.
        //

        IopExceptionCleanup( fileObject,
                             irp,
                             (PKEVENT) NULL,
                             event );

        return GetExceptionCode();
    }

    irp->Flags |= IRP_BUFFERED_IO |
                  IRP_DEALLOCATE_BUFFER |
                  IRP_INPUT_OPERATION |
                  IRP_DEFER_IO_COMPLETION;

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Parameters.QueryFile.Length = Length;
    irpSp->Parameters.QueryFile.FileInformationClass = FileInformationClass;

    //
    // Insert the packet at the head of the IRP list for the thread.
    //

    IopQueueThreadIrp( irp );

    //
    // Update the operation count statistic for the current process for
    // operations other than read and write.
    //

    IopUpdateOtherOperationCount();

    //
    // Everything is now set to invoke the device driver with this request.
    // However, it is possible that the information that the caller wants
    // is device independent.  If this is the case, then the request can
    // be satisfied here without having to have all of the drivers implement
    // the same code.  Note that having the IRP is still necessary since
    // the I/O completion code requires it.
    //

    skipDriver = FALSE;

    if (FileInformationClass == FileAccessInformation) {

        PFILE_ACCESS_INFORMATION accessBuffer = irp->AssociatedIrp.SystemBuffer;

        //
        // Return the access information for this file.
        //

        accessBuffer->AccessFlags = handleInformation.GrantedAccess;

        //
        // Complete the I/O operation.
        //

        irp->IoStatus.Information = sizeof( FILE_ACCESS_INFORMATION );
        skipDriver = TRUE;

    } else if (FileInformationClass == FileModeInformation) {

        PFILE_MODE_INFORMATION modeBuffer = irp->AssociatedIrp.SystemBuffer;

        //
        // Return the mode information for this file.
        //

        modeBuffer->Mode = IopGetModeInformation( fileObject );

        //
        // Complete the I/O operation.
        //

        irp->IoStatus.Information = sizeof( FILE_MODE_INFORMATION );
        skipDriver = TRUE;

    } else if (FileInformationClass == FileAlignmentInformation) {

        PFILE_ALIGNMENT_INFORMATION alignmentInformation = irp->AssociatedIrp.SystemBuffer;

        //
        // Return the alignment information for this file.
        //

        alignmentInformation->AlignmentRequirement = deviceObject->AlignmentRequirement;

        //
        // Complete the I/O operation.
        //

        irp->IoStatus.Information = sizeof( FILE_ALIGNMENT_INFORMATION );
        skipDriver = TRUE;

    } else if (FileInformationClass == FileAllInformation) {

        PFILE_ALL_INFORMATION allInformation = irp->AssociatedIrp.SystemBuffer;

        //
        // The caller has requested all of the information about the file.
        // This request is handled specially because the service will fill
        // in the Access and Mode and Alignment information in the buffer
        // and then pass the buffer to the driver to fill in the remainder.
        //
        // Begin by returning the Access information for the file.
        //

        allInformation->AccessInformation.AccessFlags =
            handleInformation.GrantedAccess;

        //
        // Return the mode information for this file.
        //

        allInformation->ModeInformation.Mode =
            IopGetModeInformation( fileObject );

        //
        // Return the alignment information for this file.
        //

        allInformation->AlignmentInformation.AlignmentRequirement =
            deviceObject->AlignmentRequirement;

        //
        // Finally, set the information field of the IoStatus block in the IRP
        // to account for the amount information already filled in and invoke
        // the driver to fill in the remainder.
        //

        irp->IoStatus.Information = sizeof( FILE_ACCESS_INFORMATION ) +
                                    sizeof( FILE_MODE_INFORMATION ) +
                                    sizeof( FILE_ALIGNMENT_INFORMATION );
    }

    if (skipDriver) {

        //
        // The requested operation has already been performed.  Simply
        // set the final status in the packet and the return state.
        //

        status = STATUS_SUCCESS;
        irp->IoStatus.Status = STATUS_SUCCESS;

    } else {

        //
        // This is not a request that can be [completely] performed here, so
        // invoke the driver at its appropriate dispatch entry with the IRP.
        //

        status = IoCallDriver( deviceObject, irp );
    }

    //
    // If this operation was a synchronous I/O operation, check the return
    // status to determine whether or not to wait on the file object.  If
    // the file object is to be waited on, wait for the operation to complete
    // and obtain the final status from the file object itself.
    //

    if (status == STATUS_PENDING) {

        if (synchronousIo) {

            status = KeWaitForSingleObject( &fileObject->Event,
                                            Executive,
                                            requestorMode,
                                            (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                            (PLARGE_INTEGER) NULL );

            if (status == STATUS_ALERTED || status == STATUS_USER_APC) {

                //
                // The wait request has ended either because the thread was
                // alerted or an APC was queued to this thread, because of
                // thread rundown or CTRL/C processing.  In either case, try
                // to bail out of this I/O request carefully so that the IRP
                // completes before this routine exists so that synchronization
                // with the file object will remain intact.
                //

                IopCancelAlertedRequest( &fileObject->Event, irp );

            }

            status = fileObject->FinalStatus;

            IopReleaseFileObjectLock( fileObject );

        } else {

            //
            // This is a normal synchronous I/O operation, as opposed to a
            // serialized synchronous I/O operation.  For this case, wait for
            // the local event and copy the final status information back to
            // the caller.
            //

            status = KeWaitForSingleObject( event,
                                            Executive,
                                            requestorMode,
                                            FALSE,
                                            (PLARGE_INTEGER) NULL );

            if (status == STATUS_ALERTED || status == STATUS_USER_APC) {

                //
                // The wait request has ended either because the thread was
                // alerted or an APC was queued to this thread, because of
                // thread rundown or CTRL/C processing.  In either case, try
                // to bail out of this I/O request carefully so that the IRP
                // completes before this routine exists or the event will not
                // be around to set to the Signaled state.
                //

                IopCancelAlertedRequest( event, irp );

            }

            status = localIoStatus.Status;

            try {

                *IoStatusBlock = localIoStatus;

            } except(EXCEPTION_EXECUTE_HANDLER) {

                //
                // An exception occurred attempting to write the caller's I/O
                // status block.  Simply change the final status of the operation
                // to the exception code.
                //

                status = GetExceptionCode();
            }

            ExFreePool( event );

        }

    } else {

        //
        // The I/O operation finished without return a status of pending.
        // This means that the operation has not been through I/O completion,
        // so it must be done here.
        //

        PKNORMAL_ROUTINE normalRoutine;
        PVOID normalContext;
        KIRQL irql;

        if (!synchronousIo) {

            //
            // This is not a synchronous I/O operation, it is a synchronous
            // I/O API to a file opened for asynchronous I/O.  Since this
            // code path need never wait on the allocated and supplied event,
            // get rid of it so that it doesn't have to be set to the
            // Signaled state by the I/O completion code.
            //

            irp->UserEvent = (PKEVENT) NULL;
            ExFreePool( event );
        }

        irp->UserIosb = IoStatusBlock;
        KeRaiseIrql( APC_LEVEL, &irql );
        IopCompleteRequest( &irp->Tail.Apc,
                            &normalRoutine,
                            &normalContext,
                            (PVOID *) &fileObject,
                            &normalContext );
        KeLowerIrql( irql );

        if (synchronousIo) {
            IopReleaseFileObjectLock( fileObject );
        }
    }

    return status;
}

NTSTATUS
NtSetInformationFile (
    __in HANDLE FileHandle,
    __out PIO_STATUS_BLOCK IoStatusBlock,
    __in_bcount(Length) PVOID FileInformation,
    __in ULONG Length,
    __in FILE_INFORMATION_CLASS FileInformationClass
    )

/*++

Routine Description:

    This service changes the provided information about a specified file.  The
    information that is changed is determined by the FileInformationClass that
    is specified.  The new information is taken from the FileInformation buffer.

Arguments:

    FileHandle - Supplies a handle to the file whose information should be
        changed.

    IoStatusBlock - Address of the caller's I/O status block.

    FileInformation - Supplies a buffer containing the information which should
        be changed on the file.

    Length - Supplies the length, in bytes, of the FileInformation buffer.

    FileInformationClass - Specifies the type of information which should be
        changed about the file.

Return Value:

    The status returned is the final completion status of the operation.

--*/

{
    PIRP irp;
    NTSTATUS status;
    PFILE_OBJECT fileObject;
    PDEVICE_OBJECT deviceObject;
    PKEVENT event = (PKEVENT) NULL;
    KPROCESSOR_MODE requestorMode;
    PIO_STACK_LOCATION irpSp;
    IO_STATUS_BLOCK localIoStatus = {0};
    HANDLE targetHandle = (HANDLE) NULL;
    BOOLEAN synchronousIo;
    PETHREAD CurrentThread;

    PAGED_CODE();

    //
    // Get the previous mode;  i.e., the mode of the caller.
    //

    CurrentThread = PsGetCurrentThread ();
    requestorMode = KeGetPreviousModeByThread(&CurrentThread->Tcb);

    if (requestorMode != KernelMode) {

        //
        // Ensure that the FileInformationClass parameter is legal for setting
        // information about the file.
        //

        if ((ULONG) FileInformationClass >= FileMaximumInformation ||
            !IopSetOperationLength[FileInformationClass]) {
            return STATUS_INVALID_INFO_CLASS;
        }

        //
        // Ensure that the supplied buffer is large enough to contain the
        // information associated with the specified set operation that is
        // to be performed.
        //

        if (Length < (ULONG) IopSetOperationLength[FileInformationClass]) {
            return STATUS_INFO_LENGTH_MISMATCH;
        }

        //
        // The caller's access mode is user, so probe each of the arguments
        // and capture them as necessary.  If any failures occur, the condition
        // handler will be invoked to handle them.  It will simply cleanup and
        // return an access violation status code back to the system service
        // dispatcher.
        //

        try {

            //
            // The IoStatusBlock parameter must be writeable by the caller.
            //

            ProbeForWriteIoStatus( IoStatusBlock );

            //
            // The FileInformation buffer must be readable by the caller.
            //

#if defined(_X86_)
            ProbeForRead( FileInformation,
                          Length,
                          Length == sizeof( BOOLEAN ) ? sizeof( BOOLEAN ) : sizeof( ULONG ) );
#elif defined(_WIN64)
            // If we are a wow64 process, follow the X86 rules
            if (PsGetCurrentProcessByThread(CurrentThread)->Wow64Process) {
                ProbeForRead( FileInformation,
                              Length,
                              Length == sizeof( BOOLEAN ) ? sizeof( BOOLEAN ) : sizeof( ULONG ) );
            }
            else {
                ProbeForRead( FileInformation,
                              Length,
                              IopQuerySetAlignmentRequirement[FileInformationClass] );
            }
#else
            ProbeForRead( FileInformation,
                          Length,
                          IopQuerySetAlignmentRequirement[FileInformationClass] );
#endif

        } except(EXCEPTION_EXECUTE_HANDLER) {

            //
            // An exception was incurred while probing the caller's parameters.
            // Simply return an appropriate error status code.
            //

            return GetExceptionCode();

        }

#if DBG

    } else {

        //
        // The caller's mode is kernel.  Ensure that at least the information
        // class and lengths are appropriate.
        //

        if ((ULONG) FileInformationClass >= FileMaximumInformation ||
            !IopSetOperationLength[FileInformationClass]) {
            return STATUS_INVALID_INFO_CLASS;
        }

        if (Length < (ULONG) IopSetOperationLength[FileInformationClass]) {
            return STATUS_INFO_LENGTH_MISMATCH;
        }

#endif // DBG

    }

    //
    // There were no blatant errors so far, so reference the file object so
    // the target device object can be found.  Note that if the handle does
    // not refer to a file object, or if the caller does not have the required
    // access to the file, then it will fail.
    //

    status = ObReferenceObjectByHandle( FileHandle,
                                        IopSetOperationAccess[FileInformationClass],
                                        IoFileObjectType,
                                        requestorMode,
                                        (PVOID *) &fileObject,
                                        NULL );
    if (!NT_SUCCESS( status )) {
        return status;
    }

    //
    // Get the address of the target device object.  If this file represents
    // a device that was opened directly, then simply use the device or its
    // attached device(s) directly.
    //

    if (!(fileObject->Flags & FO_DIRECT_DEVICE_OPEN)) {
        deviceObject = IoGetRelatedDeviceObject( fileObject );
    } else {
        deviceObject = IoGetAttachedDevice( fileObject->DeviceObject );
    }

    //
    // Make a special check here to determine whether this is a synchronous
    // I/O operation.  If it is, then wait here until the file is owned by
    // the current thread.  If this is not a (serialized) synchronous I/O
    // operation, then allocate and initialize the local event.
    //

    if (fileObject->Flags & FO_SYNCHRONOUS_IO) {

        BOOLEAN interrupted;

        if (!IopAcquireFastLock( fileObject )) {
            status = IopAcquireFileObjectLock( fileObject,
                                               requestorMode,
                                               (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                               &interrupted );
            if (interrupted) {
                ObDereferenceObject( fileObject );
                return status;
            }
        }

        //
        // Make a special check here to determine whether or not the caller
        // is attempting to set the file position pointer information.  If so,
        // then set it immediately and get out.
        //

        if (FileInformationClass == FilePositionInformation) {

            //
            // The caller has requested setting the current file position
            // context information.  This is a relatively frequent call, so
            // it is optimized here to cut through the normal IRP path.
            //
            // Begin by checking to see whether the file was opened with no
            // intermediate buffering.  If so, then the file pointer must be
            // set in a manner consistent with the alignment requirement of
            // read and write operations to a non-buffered file.
            //

            PFILE_POSITION_INFORMATION fileInformation = FileInformation;
            LARGE_INTEGER currentByteOffset;

            try {

                //
                // Attempt to read the position information from the buffer.
                //

                currentByteOffset.QuadPart = fileInformation->CurrentByteOffset.QuadPart;

            } except( EXCEPTION_EXECUTE_HANDLER ) {

                IopReleaseFileObjectLock( fileObject );
                ObDereferenceObject( fileObject );
                return GetExceptionCode();
            }

            if ((fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING &&
                 (deviceObject->SectorSize &&
                 (currentByteOffset.LowPart &
                 (deviceObject->SectorSize - 1)))) ||
                 currentByteOffset.HighPart < 0) {

                    status = STATUS_INVALID_PARAMETER;

            } else {

                //
                // Set the current file position information.
                //

                fileObject->CurrentByteOffset.QuadPart = currentByteOffset.QuadPart;

                try {

                    //
                    // Write the I/O status block.
                    //

                    IoStatusBlock->Status = STATUS_SUCCESS;
                    IoStatusBlock->Information = 0;

                } except( EXCEPTION_EXECUTE_HANDLER ) {

                    //
                    // Writes to I/O status blocks are ignored since the
                    // operation succeeded.
                    //

                    NOTHING;

                }

            }

            //
            // Update the transfer count statistic for the current process for
            // operations other than read and write.
            //
        
            IopUpdateOtherTransferCount( Length );

            //
            // Note that the file object's event has not yet been reset,
            // so it is not necessary to set it to the Signaled state, since
            // that is it's state at this point by definition.  Therefore,
            // simply cleanup and return.
            //

            IopReleaseFileObjectLock( fileObject );
            ObDereferenceObject( fileObject );
            return status;
        }
        synchronousIo = TRUE;
    } else {

        //
        // This is a synchronous API being invoked for a file that is opened
        // for asynchronous I/O.  This means that this system service is
        // to synchronize the completion of the operation before returning
        // to the caller.  A local event is used to do this.
        //

        event = ExAllocatePool( NonPagedPool, sizeof( KEVENT ) );
        if (event == NULL) {
            ObDereferenceObject( fileObject );
            return STATUS_INSUFFICIENT_RESOURCES;
        }
        KeInitializeEvent( event, SynchronizationEvent, FALSE );
        synchronousIo = FALSE;
    }

    //
    // Set the file object to the Not-Signaled state.
    //

    KeClearEvent( &fileObject->Event );

    //
    // If a link is being tracked, handle this out-of-line.
    //

    if (FileInformationClass == FileTrackingInformation) {
        status = IopTrackLink( fileObject,
                               &localIoStatus,
                               FileInformation,
                               Length,
                               synchronousIo ? &fileObject->Event : event,
                               requestorMode );
        if (NT_SUCCESS( status )) {
            try {
                IoStatusBlock->Information = 0;
                IoStatusBlock->Status = status;
            } except(EXCEPTION_EXECUTE_HANDLER) {
                NOTHING;
            }
        }

        if (synchronousIo) {
            IopReleaseFileObjectLock( fileObject );
        } else {
            ExFreePool( event );
        }
        ObDereferenceObject( fileObject );
        return status;
    }

    //
    // Allocate and initialize the I/O Request Packet (IRP) for this operation.
    // The allocation is performed with an exception handler in case the
    // caller does not have enough quota to allocate the packet.

    irp = IoAllocateIrp( deviceObject->StackSize, !synchronousIo );
    if (!irp) {

        //
        // An IRP could not be allocated.  Cleanup and return an appropriate
        // error status code.
        //

        if (!(fileObject->Flags & FO_SYNCHRONOUS_IO)) {
            ExFreePool( event );
        }

        IopAllocateIrpCleanup( fileObject, (PKEVENT) NULL );

        return STATUS_INSUFFICIENT_RESOURCES;
    }
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = CurrentThread;
    irp->RequestorMode = requestorMode;

    //
    // Fill in the service independent parameters in the IRP.
    //

    if (synchronousIo) {
        irp->UserEvent = (PKEVENT) NULL;
        irp->UserIosb = IoStatusBlock;
    } else {
        irp->UserEvent = event;
        irp->UserIosb = &localIoStatus;
        irp->Flags = IRP_SYNCHRONOUS_API;
    }
    irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE) NULL;

    //
    // Get a pointer to the stack location for the first driver.  This will
    // be used to pass the original function codes and parameters.
    //

    irpSp = IoGetNextIrpStackLocation( irp );
    irpSp->MajorFunction = IRP_MJ_SET_INFORMATION;
    irpSp->FileObject = fileObject;

    //
    // Allocate a buffer and copy the information that is to be set on the
    // file into it.  Also, set the flags so that the completion code will
    // properly handle getting rid of the buffer and will not attempt to
    // copy data.
    //

    irp->AssociatedIrp.SystemBuffer = (PVOID) NULL;
    irp->MdlAddress = (PMDL) NULL;

    try {

        PVOID systemBuffer;

        systemBuffer =
        irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithQuota( NonPagedPool,
                                                                   Length );
        RtlCopyMemory( irp->AssociatedIrp.SystemBuffer,
                       FileInformation,
                       Length );

        //
        //  Negative file offsets are illegal.
        //

        ASSERT((FIELD_OFFSET(FILE_END_OF_FILE_INFORMATION, EndOfFile) |
                FIELD_OFFSET(FILE_ALLOCATION_INFORMATION, AllocationSize) |
                FIELD_OFFSET(FILE_POSITION_INFORMATION, CurrentByteOffset)) == 0);

        if (((FileInformationClass == FileEndOfFileInformation) ||
             (FileInformationClass == FileAllocationInformation) ||
             (FileInformationClass == FilePositionInformation)) &&
            (((PFILE_POSITION_INFORMATION)systemBuffer)->CurrentByteOffset.HighPart < 0)) {

            ExRaiseStatus(STATUS_INVALID_PARAMETER);
        }



    } except(EXCEPTION_EXECUTE_HANDLER) {

        //
        // An exception was incurred while allocating the intermediary
        // system buffer or while copying the caller's data into the
        // buffer. Cleanup and return an appropriate error status code.
        //

        IopExceptionCleanup( fileObject,
                             irp,
                             (PKEVENT) NULL,
                             event );

        return GetExceptionCode();

    }

    irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_DEFER_IO_COMPLETION;

    //
    // Copy the caller's parameters to the service-specific portion of the
    // IRP.
    //

    irpSp->Parameters.SetFile.Length = Length;
    irpSp->Parameters.SetFile.FileInformationClass = FileInformationClass;

    //
    // Insert the packet at the head of the IRP list for the thread.
    //

    IopQueueThreadIrp( irp );

    //
    // Update the operation count statistic for the current process for
    // operations other than read and write.
    //

    IopUpdateOtherOperationCount();


    //
    // Everything is now set to invoke the device driver with this request.
    // However, it is possible that the information that the caller wants
    // to set is device independent.  If this is the case, then the request
    // can be satisfied here without having to have all of the drivers
    // implement the same code.  Note that having the IRP is still necessary
    // since the I/O completion code requires it.
    //

    if (FileInformationClass == FileModeInformation) {

        PFILE_MODE_INFORMATION modeBuffer = irp->AssociatedIrp.SystemBuffer;

        //
        // Set the various flags in the mode field for the file object, if
        // they are reasonable.  There are 4 different invalid combinations
        // that the caller may not specify:
        //
        //     1)  An invalid flag was set in the mode field.  Not all Create/
        //         Open options may be changed.
        //
        //     2)  The caller set one of the synchronous I/O flags (alert or
        //         nonalert), but the file is not opened for synchronous I/O.
        //
        //     3)  The file is opened for synchronous I/O but the caller did
        //         not set either of the synchronous I/O flags (alert or non-
        //         alert).
        //
        //     4)  The caller set both of the synchronous I/O flags (alert and
        //         nonalert).
        //

        if ((modeBuffer->Mode & ~FILE_VALID_SET_FLAGS) ||
            ((modeBuffer->Mode & (FSIO_A | FSIO_NA)) && (!(fileObject->Flags & FO_SYNCHRONOUS_IO))) ||
            ((!(modeBuffer->Mode & (FSIO_A | FSIO_NA))) && (fileObject->Flags & FO_SYNCHRONOUS_IO)) ||
            (((modeBuffer->Mode & FSIO_A) && (modeBuffer->Mode & FSIO_NA) ))) {
            status = STATUS_INVALID_PARAMETER;

        } else {

            //
            // Set or clear the appropriate flags in the file object.
            //

            if (!(fileObject->Flags & FO_NO_INTERMEDIATE_BUFFERING)) {
                if (modeBuffer->Mode & FILE_WRITE_THROUGH) {
                    fileObject->Flags |= FO_WRITE_THROUGH;
                } else {
                    fileObject->Flags &= ~FO_WRITE_THROUGH;
                }
            }

            if (modeBuffer->Mode & FILE_SEQUENTIAL_ONLY) {
                fileObject->Flags |= FO_SEQUENTIAL_ONLY;
            } else {
                fileObject->Flags &= ~FO_SEQUENTIAL_ONLY;
            }

            if (fileObject->Flags & FO_SYNCHRONOUS_IO) {
                if (modeBuffer->Mode & FSIO_A) {
                    fileObject->Flags |= FO_ALERTABLE_IO;
                } else {
                    fileObject->Flags &= ~FO_ALERTABLE_IO;
                }
            }

            status = STATUS_SUCCESS;
        }

        //
        // Complete the I/O operation.
        //

        irp->IoStatus.Status = status;
        irp->IoStatus.Information = 0L;

    } else if (FileInformationClass == FileRenameInformation ||
               FileInformationClass == FileLinkInformation ||
               FileInformationClass == FileMoveClusterInformation) {

        //
        // Note that following code depends on the fact that the rename
        // information, link information and copy-on-write information
        // structures look exactly the same.
        //

        PFILE_RENAME_INFORMATION renameBuffer = irp->AssociatedIrp.SystemBuffer;

        //
        // The information being set is a variable-length structure with
        // embedded size information.  Walk the structure to ensure that
        // it is valid so the driver does not walk off the end and incur
        // an access violation in kernel mode.
        //
 
        if (renameBuffer->FileNameLength <= 0 || (renameBuffer->FileNameLength & (sizeof(WCHAR) -1))) {
            status = STATUS_INVALID_PARAMETER;
            irp->IoStatus.Status = status;
        } else if ((ULONG) (Length - FIELD_OFFSET( FILE_RENAME_INFORMATION, FileName[0] )) < renameBuffer->FileNameLength) {
            status = STATUS_INVALID_PARAMETER;
            irp->IoStatus.Status = status;

        } else {

            //
            // Copy the value of the replace BOOLEAN (or the ClusterCount field)
            // from the caller's buffer to the I/O stack location parameter
            // field where it is expected by file systems.
            //

            if (FileInformationClass == FileMoveClusterInformation) {
                irpSp->Parameters.SetFile.ClusterCount =
                    ((FILE_MOVE_CLUSTER_INFORMATION *) renameBuffer)->ClusterCount;
            } else {
                irpSp->Parameters.SetFile.ReplaceIfExists = renameBuffer->ReplaceIfExists;
            }

            //
            // Check to see whether or not a fully qualified pathname was
            // supplied.  If so, then more processing is required.
            //

            if (renameBuffer->FileName[0] == (WCHAR) OBJ_NAME_PATH_SEPARATOR ||
                renameBuffer->RootDirectory) {

                //
                // A fully qualified file name was specified as the target of
                // the rename operation.  Attempt to open the target file and
                // ensure that the replacement policy for the file is consistent
                // with the caller's request, and ensure that the file is on the
                // same volume.
                //

                status = IopOpenLinkOrRenameTarget( &targetHandle,
                                                    irp,
                                                    renameBuffer,
                                                    fileObject );
                if (!NT_SUCCESS( status )) {
                    irp->IoStatus.Status = status;

                } else {

                    //
                    // The fully qualified file name specifies a file on the
                    // same volume and if it exists, then the caller specified
                    // that it should be replaced.
                    //

                    status = IoCallDriver( deviceObject, irp );

                }

            } else {

                //
                // This is a simple rename operation, so call the driver and
                // let it perform the rename operation within the same directory
                // as the source file.
                //

                status = IoCallDriver( deviceObject, irp );

            }
        }

    } else if (FileInformationClass == FileShortNameInformation) {

        PFILE_NAME_INFORMATION shortnameBuffer = irp->AssociatedIrp.SystemBuffer;

        //
        // The information being set is a variable-length structure with
        // embedded size information.  Walk the structure to ensure that
        // it is valid so the driver does not walk off the end and incur
        // an access violation in kernel mode.
        //
 
        if (shortnameBuffer->FileNameLength <= 0) {
            status = STATUS_INVALID_PARAMETER;
            irp->IoStatus.Status = status;
        } else if ((ULONG) (Length - FIELD_OFFSET( FILE_NAME_INFORMATION, FileName[0] )) < shortnameBuffer->FileNameLength) {
            status = STATUS_INVALID_PARAMETER;
            irp->IoStatus.Status = status;

        //
        // The short name must not begin with a separator character.
        //

        } else if (shortnameBuffer->FileName[0] == (WCHAR) OBJ_NAME_PATH_SEPARATOR) {

            status = STATUS_INVALID_PARAMETER;
            irp->IoStatus.Status = status;

        //
        // Pass the request to the driver below.
        //

        } else {

            status = IoCallDriver( deviceObject, irp );
        }

    } else if (FileInformationClass == FileDispositionInformation) {

        PFILE_DISPOSITION_INFORMATION disposition = irp->AssociatedIrp.SystemBuffer;

        //
        // Check to see whether the disposition delete field has been set to
        // TRUE and, if so, copy the handle being used to do this to the IRP
        // stack location parameter.
        //

        if (disposition->DeleteFile) {
            irpSp->Parameters.SetFile.DeleteHandle = FileHandle;
        }

        //
        // Simply invoke the driver to perform the (un)delete operation.
        //

        status = IoCallDriver( deviceObject, irp );

    } else if (FileInformationClass == FileCompletionInformation) {

        PFILE_COMPLETION_INFORMATION completion = irp->AssociatedIrp.SystemBuffer;
        PIO_COMPLETION_CONTEXT context;
        PVOID portObject;

        //
        // It is an error if this file object already has an LPC port associated
        // with it.
        //

        if (fileObject->CompletionContext || fileObject->Flags & FO_SYNCHRONOUS_IO) {

            status = STATUS_INVALID_PARAMETER;

        } else {

            //
            // Attempt to reference the port object by its handle and convert it
            // into a pointer to the port object itself.
            //

            status = ObReferenceObjectByHandle( completion->Port,
                                                IO_COMPLETION_MODIFY_STATE,
                                                IoCompletionObjectType,
                                                requestorMode,
                                                (PVOID *) &portObject,
                                                NULL );
            if (NT_SUCCESS( status )) {

                //
                // Allocate the memory to be associated w/this file object
                //

                context = ExAllocatePoolWithTag( PagedPool,
                                                 sizeof( IO_COMPLETION_CONTEXT ),
                                                 'cCoI' );
                if (!context) {

                    ObDereferenceObject( portObject );
                    status = STATUS_INSUFFICIENT_RESOURCES;

                } else {

                    //
                    // Everything was successful.  Capture the completion port
                    // and the key.
                    //

                    context->Port = portObject;
                    context->Key = completion->Key;

                    if (!InterlockedCompareExchangePointer( &fileObject->CompletionContext, context, NULL )) {

                        status = STATUS_SUCCESS;

                    } else {

                        //
                        // Someone set the completion context after the check.
                        // Simply drop everything on the floor and return an
                        // error.
                        //

                        ExFreePool( context );
                        ObDereferenceObject( portObject );
                        status = STATUS_PORT_ALREADY_SET;
                    }
                }
            }
        }

        //
        // Complete the I/O operation.
        //

        irp->IoStatus.Status = status;
        irp->IoStatus.Information = 0;

    } else {

        //
        // This is not a request that can be performed here, so invoke the
        // driver at its appropriate dispatch entry with the IRP.
        //

        status = IoCallDriver( deviceObject, irp );
    }

    //
    // If this operation was a synchronous I/O operation, check the return
    // status to determine whether or not to wait on the file object.  If
    // the file object is to be waited on, wait for the operation to complete
    // and obtain the final status from the file object itself.
    //

    if (status == STATUS_PENDING) {

        if (synchronousIo) {

            status = KeWaitForSingleObject( &fileObject->Event,
                                            Executive,
                                            requestorMode,
                                            (BOOLEAN) ((fileObject->Flags & FO_ALERTABLE_IO) != 0),
                                            (PLARGE_INTEGER) NULL );

            if (status == STATUS_ALERTED || status == STATUS_USER_APC) {

                //
                // The wait request has ended either because the thread was
                // alerted or an APC was queued to this thread, because of
                // thread rundown or CTRL/C processing.  In either case, try
                // to bail out of this I/O request carefully so that the IRP
                // completes before this routine exists so that synchronization
                // with the file object will remain intact.
                //

                IopCancelAlertedRequest( &fileObject->Event, irp );

            }

            status = fileObject->FinalStatus;

            IopReleaseFileObjectLock( fileObject );

        } else {

            //
            // This is a normal synchronous I/O operation, as opposed to a
            // serialized synchronous I/O operation.  For this case, wait for
            // the local event and copy the final status information back to
            // the caller.
            //

            status = KeWaitForSingleObject( event,
                                            Executive,
                                            requestorMode,
                                            FALSE,
                                            (PLARGE_INTEGER) NULL );

            if (status == STATUS_ALERTED || status == STATUS_USER_APC) {

                //
                // The wait request has ended either because the thread was
                // alerted or an APC was queued to this thread, because of
                // thread rundown or CTRL/C processing.  In either case, try
                // to bail out of this I/O request carefully so that the IRP
                // completes before this routine exists or the event will not
                // be around to set to the Signaled state.
                //

                IopCancelAlertedRequest( event, irp );

            }

            status = localIoStatus.Status;

            try {

                *IoStatusBlock = localIoStatus;

            } except(EXCEPTION_EXECUTE_HANDLER) {

                //
                // An exception occurred attempting to write the caller's I/O
                // status block.  Simply change the final status of the
                // operation to the exception code.
                //

                status = GetExceptionCode();
            }

            ExFreePool( event );

        }

    } else {

        //
        // The I/O operation finished without return a status of pending.
        // This means that the operation has not been through I/O completion,
        // so it must be done here.
        //

        PKNORMAL_ROUTINE normalRoutine;
        PVOID normalContext;
        KIRQL irql;

        if (!synchronousIo) {

            //
            // This is not a synchronous I/O operation, it is a synchronous
            // I/O API to a file opened for asynchronous I/O.  Since this
            // code path need never wait on the allocated and supplied event,
            // get rid of it so that it doesn't have to be set to the
            // Signaled state by the I/O completion code.
            //

            irp->UserEvent = (PKEVENT) NULL;
            ExFreePool( event );
        }

        irp->UserIosb = IoStatusBlock;
        KeRaiseIrql( APC_LEVEL, &irql );
        IopCompleteRequest( &irp->Tail.Apc,
                            &normalRoutine,
                            &normalContext,
                            (PVOID *) &fileObject,
                            &normalContext );
        KeLowerIrql( irql );

        if (synchronousIo) {
            IopReleaseFileObjectLock( fileObject );
        }

    }

    //
    // If there was a target handle generated because of a rename operation,
    // close it now.
    //

    if (targetHandle) {
        ObCloseHandle( targetHandle, KernelMode );
    }

    return status;
}

