/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    obclose.c

Abstract:

    Object close system service

Author:

    Steve Wood (stevewo) 31-Mar-1989

Revision History:

--*/

#include "obp.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtMakeTemporaryObject)
#pragma alloc_text(PAGE,NtClose)
#pragma alloc_text(PAGE,ObMakeTemporaryObject)
#pragma alloc_text(PAGE,ObpCloseHandleTableEntry)
#pragma alloc_text(PAGE,ObCloseHandle)
#pragma alloc_text(PAGE,ObpCloseHandle)
#endif

//
//  Indicates if auditing is enabled so we have to close down the object
//  audit alarm
//

extern BOOLEAN SepAdtAuditingEnabled;

ObpCloseHandleTableEntry (
    IN PHANDLE_TABLE ObjectTable,
    IN PHANDLE_TABLE_ENTRY ObjectTableEntry,
    IN HANDLE Handle,
    IN KPROCESSOR_MODE PreviousMode,
    IN BOOLEAN Rundown,
    IN BOOLEAN CanNotRaise
    )
/*++

Routine Description:

    This function is used to close a handle table entry

Arguments:

    ObjectTableEntry - Supplies the entry being closed. It must be locked
    PreviousMode     - Mode of caller
    Rundown          - Called as part of process rundown, ignore protected handles in this mode
    CanNotRaise      - Can not raise user exceptions

Return Value:

    An appropriate status value

--*/
{
    POBJECT_HEADER ObjectHeader;
    POBJECT_TYPE ObjectType;
    PVOID Object;
    ULONG CapturedGrantedAccess;
    ULONG CapturedAttributes;
    #if DBG
    KIRQL SaveIrql;
    #endif // DBG

    //
    //  From the object table entry we can grab a pointer to the object
    //  header, get its type and its body
    //

    ObjectHeader = (POBJECT_HEADER)(((ULONG_PTR)(ObjectTableEntry->Object)) & ~OBJ_HANDLE_ATTRIBUTES);
    ObjectType = ObjectHeader->Type;
    Object = &ObjectHeader->Body;

    //
    //  If the object type specifies an okay to close procedure then we
    //  need to invoke that callback.  If the callback doesn't want us to
    //  close handle then unlock the object table and return the error
    //  to our caller
    //

    if (ObjectType->TypeInfo.OkayToCloseProcedure != NULL) {

        ObpBeginTypeSpecificCallOut( SaveIrql );

        if (!(*ObjectType->TypeInfo.OkayToCloseProcedure)( PsGetCurrentProcess(),
                                                           Object,
                                                           Handle,
                                                           PreviousMode )) {

            ObpEndTypeSpecificCallOut( SaveIrql, "NtClose", ObjectType, Object );

            ExUnlockHandleTableEntry( ObjectTable, ObjectTableEntry );

            return STATUS_HANDLE_NOT_CLOSABLE;
        }

        ObpEndTypeSpecificCallOut( SaveIrql, "NtClose", ObjectType, Object );
    }

    CapturedAttributes = ObpGetHandleAttributes(ObjectTableEntry);

    //
    //  If the previous mode was user and the handle is protected from
    //  being closed, then we'll either raise or return an error depending
    //  on the global flags and debugger port situation.
    //

    if ((CapturedAttributes & OBJ_PROTECT_CLOSE) != 0 && Rundown == FALSE) {

        if (PreviousMode != KernelMode) {
            PETHREAD CurrentThread;

            ExUnlockHandleTableEntry( ObjectTable, ObjectTableEntry );

            CurrentThread = PsGetCurrentThread ();

            if (!CanNotRaise && !KeIsAttachedProcess() &&
                !PsIsThreadTerminating (CurrentThread) &&
                !CurrentThread->Tcb.ApcState.KernelApcInProgress &&
                ((NtGlobalFlag & FLG_ENABLE_CLOSE_EXCEPTIONS) ||
                 (PsGetCurrentProcess()->DebugPort != NULL))) {

                //
                //  Raise and exception in user mode
                //
                return KeRaiseUserException(STATUS_HANDLE_NOT_CLOSABLE);

            } else {

                return STATUS_HANDLE_NOT_CLOSABLE;
            }

        } else {
            KeBugCheckEx(INVALID_KERNEL_HANDLE, (ULONG_PTR)Handle, 0, 0, 0);
        }
    }
    
    //
    //  Get the granted access for the handle
    //

#if i386 

    if (NtGlobalFlag & FLG_KERNEL_STACK_TRACE_DB) {

        CapturedGrantedAccess = ObpTranslateGrantedAccessIndex( ObjectTableEntry->GrantedAccessIndex );

    } else {

        CapturedGrantedAccess = ObpDecodeGrantedAccess(ObjectTableEntry->GrantedAccess);
    }

#else

    CapturedGrantedAccess = ObpDecodeGrantedAccess(ObjectTableEntry->GrantedAccess);

#endif // i386 

    //
    //  Now remove the handle from the handle table
    //

    ExDestroyHandle( ObjectTable,
                     Handle,
                     ObjectTableEntry );

    //
    //  perform any auditing required
    //

    //
    //  Extract the value of the GenerateOnClose bit stored
    //  after object open auditing is performed.  This value
    //  was stored by a call to ObSetGenerateOnClosed.
    //

    if (CapturedAttributes & OBJ_AUDIT_OBJECT_CLOSE) {

        if ( SepAdtAuditingEnabled ) {

            SeCloseObjectAuditAlarm( Object,
                                     (HANDLE)((ULONG_PTR)Handle & ~OBJ_HANDLE_TAGBITS),  // Mask off the tagbits defined for OB objects.
                                     TRUE );
        }
    }

    //
    //  Since we took the handle away we need to decrement the objects
    //  handle count, and remove a reference
    //

    ObpDecrementHandleCount( PsGetCurrentProcess(),
                             ObjectHeader,
                             ObjectType,
                             CapturedGrantedAccess );

    ObDereferenceObject( Object );

    //
    //  And return to our caller
    //

    return STATUS_SUCCESS;
}


NTSTATUS
ObpCloseHandle (
    IN HANDLE Handle,
    IN KPROCESSOR_MODE PreviousMode,
    IN BOOLEAN CanNotRaise
    )
/*++

Routine Description:

    This function is used to close access to the specified handle with the given mode

Arguments:

    Handle - Supplies the handle being closed
    PreviousMode - Processor mode to be used in the handle access checks.
    CanNotRaise - We are not allowed to do a user mode raise.

Return Value:

    An appropriate status value

--*/
{
    PHANDLE_TABLE ObjectTable;
    PHANDLE_TABLE_ENTRY ObjectTableEntry;
    NTSTATUS Status;
    BOOLEAN AttachedToProcess = FALSE;
    KAPC_STATE ApcState;
    PETHREAD CurrentThread;
    PEPROCESS CurrentProcess;


    ObpValidateIrql( "NtClose" );

    CurrentThread = PsGetCurrentThread ();
    CurrentProcess = PsGetCurrentProcessByThread (CurrentThread);
    //
    //  For the current process we will grab its handle/object table and
    //  translate the handle to its corresponding table entry.  If the
    //  call is successful it also lock down the handle table.  But first
    //  check for a kernel handle and attach and use that table if so.
    //

    if (IsKernelHandle( Handle, PreviousMode ))  {

        Handle = DecodeKernelHandle( Handle );

        ObjectTable = ObpKernelHandleTable;

        //
        //  Go to the system process if we have to
        //
        if (CurrentProcess != PsInitialSystemProcess) {
           KeStackAttachProcess (&PsInitialSystemProcess->Pcb, &ApcState);
           AttachedToProcess = TRUE;
        }

    } else {

        ObjectTable = CurrentProcess->ObjectTable;
    }

    //
    //  Protect ourselves from being interrupted while we hold a handle table
    //  entry lock
    //

    KeEnterCriticalRegionThread(&CurrentThread->Tcb);

    ObjectTableEntry = ExMapHandleToPointer( ObjectTable,
                                             Handle );

    //
    //  Check that the specified handle is legitimate otherwise we can
    //  assume the caller just passed in some bogus handle value
    //

    if (ObjectTableEntry != NULL) {

        Status = ObpCloseHandleTableEntry (ObjectTable, ObjectTableEntry, Handle, PreviousMode, FALSE, CanNotRaise);

        KeLeaveCriticalRegionThread(&CurrentThread->Tcb);
        //
        //  If we are attached to the system process then detach
        //
        if (AttachedToProcess) {

            KeUnstackDetachProcess(&ApcState);
            AttachedToProcess = FALSE;
        }


    } else {

        KeLeaveCriticalRegionThread(&CurrentThread->Tcb);

        //
        //  At this point the input handle did not translate to a valid
        //  object table entry
        //

        //
        //  If we are attached to the system process then return
        //  back to our caller
        //

        if (AttachedToProcess) {
            KeUnstackDetachProcess(&ApcState);
            AttachedToProcess = FALSE;
        }

        //
        //  Now if the handle is not null and it does not represent the
        //  current thread or process then if we're user mode we either raise
        //  or return an error
        //

        if ((Handle != NULL) &&
            (Handle != NtCurrentThread()) &&
            (Handle != NtCurrentProcess())) {

            if (PreviousMode != KernelMode) {

                if ((NtGlobalFlag & FLG_ENABLE_CLOSE_EXCEPTIONS) ||
                    (CurrentProcess->DebugPort != NULL)) {

                    if (!CanNotRaise && !KeIsAttachedProcess() &&
                        !PsIsThreadTerminating (CurrentThread) &&
                        !CurrentThread->Tcb.ApcState.KernelApcInProgress) {
                        return KeRaiseUserException (STATUS_INVALID_HANDLE);
                    } else {
                        return STATUS_INVALID_HANDLE;
                    }

                }

            } else {

                //
                //  bugcheck here if kernel debugger is enabled and if kernel mode code is
                //  closing a bogus handle and process is not exiting.  Ignore
                //  if no PEB as this occurs if process is killed before
                //  really starting.
                //

                if (( !PsIsThreadTerminating(CurrentThread)) &&
                    (CurrentProcess->Peb != NULL)) {

                    if (KdDebuggerEnabled) {
                        KeBugCheckEx(INVALID_KERNEL_HANDLE, (ULONG_PTR)Handle, 1, 0, 0);
                    }
                }

            }
        }

        Status = STATUS_INVALID_HANDLE;
    }


    return Status;
}

NTSTATUS
ObCloseHandle (
    IN HANDLE Handle,
    IN KPROCESSOR_MODE PreviousMode
    )
/*++

Routine Description:

    This function is used to close access to the specified handle with the given mode

Arguments:

    Handle - Supplies the handle being closed
    PreviousMode - Processor mode to be used in the handle access checks.

Return Value:

    An appropriate status value

--*/
{
    return ObpCloseHandle (Handle,
                           PreviousMode,
                           TRUE);
}


NTSTATUS
NtClose (
    IN HANDLE Handle
    )

/*++

Routine Description:

    This function is used to close access to the specified handle

Arguments:

    Handle - Supplies the handle being closed

Return Value:

    An appropriate status value

--*/

{
    return ObpCloseHandle (Handle,
                           KeGetPreviousMode (),
                           FALSE);
}


NTSTATUS
NtMakeTemporaryObject (
    IN HANDLE Handle
    )

/*++

Routine Description:

    This routine makes the specified object non permanent.

Arguments:

    Handle - Supplies a handle to the object being modified

Return Value:

    An appropriate status value.

--*/

{
    KPROCESSOR_MODE PreviousMode;
    NTSTATUS Status;
    PVOID Object;
    OBJECT_HANDLE_INFORMATION HandleInformation;

    PAGED_CODE();

    //
    //  Get previous processor mode and probe output argument if necessary.
    //

    PreviousMode = KeGetPreviousMode();

    Status = ObReferenceObjectByHandle( Handle,
                                        DELETE,
                                        (POBJECT_TYPE)NULL,
                                        PreviousMode,
                                        &Object,
                                        &HandleInformation );
    if (!NT_SUCCESS( Status )) {

        return( Status );
    }

    //
    //  Make the object temporary.  Note that the object should still
    //  have a name and directory entry because its handle count is not
    //  zero
    //

    ObMakeTemporaryObject( Object );

    //
    //  Check if we need to generate a delete object audit/alarm
    //

    if (HandleInformation.HandleAttributes & OBJ_AUDIT_OBJECT_CLOSE) {

        SeDeleteObjectAuditAlarm( Object,
                                  Handle );
    }

    ObDereferenceObject( Object );

    return( Status );
}


VOID
ObMakeTemporaryObject (
    IN PVOID Object
    )

/*++

Routine Description:

    This routine removes the name of the object from its parent
    directory.  The object is only removed if it has a non zero
    handle count and a name.  Otherwise the object is simply
    made non permanent

Arguments:

    Object - Supplies the object being modified

Return Value:

    None.

--*/

{
    POBJECT_HEADER ObjectHeader;
    POBJECT_TYPE ObjectType;

    PAGED_CODE();


    ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object );
    ObjectType = ObjectHeader->Type;

    //
    // Other bits are set in this flags field by the handle database code. Synchronise with that.
    //
    ObpLockObject( ObjectHeader );

    ObjectHeader->Flags &= ~OB_FLAG_PERMANENT_OBJECT;

    ObpUnlockObject( ObjectHeader );

    //
    // Now delete the object name if no more handles are present.
    //
    ObpDeleteNameCheck( Object );

    return;
}

