/*
 * PROJECT:         ReactOS Kernel
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            ntoskrnl/ex/harderr.c
 * PURPOSE:         Error Functions and Status/Exception Dispatching/Raising
 * PROGRAMMERS:     Alex Ionescu (alex@relsoft.net)
 */

/* INCLUDES *****************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>

/* GLOBALS ******************************************************************/

BOOLEAN ExReadyForErrors = FALSE;
PVOID ExpDefaultErrorPort = NULL;
PEPROCESS ExpDefaultErrorPortProcess = NULL;

/* FUNCTIONS ****************************************************************/

/*++
* @name ExpSystemErrorHandler
*
* For now it's a stub
*
* @param ErrorStatus
*        FILLME
*
* @param NumberOfParameters
*        FILLME
*
* @param UnicodeStringParameterMask
*        FILLME
*
* @param Parameters
*        FILLME
*
* @param ValidResponseOptions
*        FILLME
*
* @param Response
*        FILLME
*
* @return None
*
* @remarks None
*
*--*/
NTSTATUS
NTAPI
ExpSystemErrorHandler(IN NTSTATUS ErrorStatus,
                      IN ULONG NumberOfParameters,
                      IN ULONG UnicodeStringParameterMask,
                      IN PULONG_PTR Parameters,
                      IN BOOLEAN Shutdown)
{
    ULONG_PTR BugCheckParameters[MAXIMUM_HARDERROR_PARAMETERS] = {0, 0, 0, 0};
    ULONG i;

    /* Sanity check */
    ASSERT(NumberOfParameters <= MAXIMUM_HARDERROR_PARAMETERS);

    /*
     * KeBugCheck expects MAXIMUM_HARDERROR_PARAMETERS parameters,
     * but we might get called with less, so use a local buffer here.
     */
    for (i = 0; i < NumberOfParameters; i++)
    {
        /* Copy them over */
        BugCheckParameters[i] = Parameters[i];
    }

    /* FIXME: STUB */
    KeBugCheckEx(FATAL_UNHANDLED_HARD_ERROR,
                 ErrorStatus,
                 (ULONG_PTR)BugCheckParameters,
                 0,
                 0);
    return STATUS_SUCCESS;
}

/*++
 * @name ExpRaiseHardError
 * @implemented
 *
 * See ExRaiseHardError and NtRaiseHardError, same parameters.
 *
 * This function performs the central work for both ExRaiseHardError
 * and NtRaiseHardError. ExRaiseHardError is the service for kernel-mode
 * that copies the parameters to user-mode, and NtRaiseHardError is the
 * service for both kernel-mode and user-mode that performs parameters
 * validation and capture if necessary.
 *
 *--*/
NTSTATUS
NTAPI
ExpRaiseHardError(IN NTSTATUS ErrorStatus,
                  IN ULONG NumberOfParameters,
                  IN ULONG UnicodeStringParameterMask,
                  IN PULONG_PTR Parameters,
                  IN ULONG ValidResponseOptions,
                  OUT PULONG Response)
{
    NTSTATUS Status;
    PEPROCESS Process = PsGetCurrentProcess();
    PETHREAD Thread = PsGetCurrentThread();
    UCHAR Buffer[PORT_MAXIMUM_MESSAGE_LENGTH];
    PHARDERROR_MSG Message = (PHARDERROR_MSG)Buffer;
    HANDLE PortHandle;
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();

    PAGED_CODE();

    /* Check if this error will shutdown the system */
    if (ValidResponseOptions == OptionShutdownSystem)
    {
        /*
         * Check if we have the privileges.
         *
         * NOTE: In addition to the Shutdown privilege we also check whether
         * the caller has the Tcb privilege. The purpose is to allow only
         * SYSTEM processes to "shutdown" the system on hard errors (BSOD)
         * while forbidding regular processes to do so. This behaviour differs
         * from Windows, where any user-mode process, as soon as it has the
         * Shutdown privilege, can trigger a hard-error BSOD.
         */
        if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode) ||
            !SeSinglePrivilegeCheck(SeShutdownPrivilege, PreviousMode))
        {
            /* No rights */
            *Response = ResponseNotHandled;
            return STATUS_PRIVILEGE_NOT_HELD;
        }

        /* Don't handle any new hard errors */
        ExReadyForErrors = FALSE;
    }

    /* Check if hard errors are not disabled */
    if (!Thread->HardErrorsAreDisabled)
    {
        /* Check if we can't do errors anymore, and this is serious */
        if (!ExReadyForErrors && NT_ERROR(ErrorStatus))
        {
            /* Use the system handler */
            ExpSystemErrorHandler(ErrorStatus,
                                  NumberOfParameters,
                                  UnicodeStringParameterMask,
                                  Parameters,
                                  (PreviousMode != KernelMode) ? TRUE : FALSE);
        }
    }

    /*
     * Enable hard error processing if it is enabled for the process
     * or if the exception status forces it.
     */
    if ((Process->DefaultHardErrorProcessing & SEM_FAILCRITICALERRORS) ||
        (ErrorStatus & HARDERROR_OVERRIDE_ERRORMODE))
    {
        /* Check if we have an exception port */
        if (Process->ExceptionPort)
        {
            /* Use the port */
            PortHandle = Process->ExceptionPort;
        }
        else
        {
            /* Use our default system port */
            PortHandle = ExpDefaultErrorPort;
        }
    }
    else
    {
        /* Don't process the error */
        PortHandle = NULL;
    }

    /* If hard errors are disabled, do nothing */
    if (Thread->HardErrorsAreDisabled) PortHandle = NULL;

    /*
     * If this is not the system thread, check whether hard errors are
     * disabled for this thread on user-mode side, and if so, do nothing.
     */
    if (!Thread->SystemThread && (PortHandle != NULL))
    {
        /* Check if we have a TEB */
        PTEB Teb = PsGetCurrentThread()->Tcb.Teb;
        if (Teb)
        {
            _SEH2_TRY
            {
                if (Teb->HardErrorMode & RTL_SEM_FAILCRITICALERRORS)
                {
                    PortHandle = NULL;
                }
            }
            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
            {
                NOTHING;
            }
            _SEH2_END;
        }
    }

    /* Now check if we have a port */
    if (PortHandle == NULL)
    {
        /* Just return to caller */
        *Response = ResponseReturnToCaller;
        return STATUS_SUCCESS;
    }

    /* Check if this is the default process */
    if (Process == ExpDefaultErrorPortProcess)
    {
        /* We can't handle the error, check if this is critical */
        if (NT_ERROR(ErrorStatus))
        {
            /* It is, invoke the system handler */
            ExpSystemErrorHandler(ErrorStatus,
                                  NumberOfParameters,
                                  UnicodeStringParameterMask,
                                  Parameters,
                                  (PreviousMode != KernelMode) ? TRUE : FALSE);

            /* If we survived, return to caller */
            *Response = ResponseReturnToCaller;
            return STATUS_SUCCESS;
        }
    }

    /* Setup the LPC Message */
    Message->h.u1.Length = (sizeof(HARDERROR_MSG) << 16) |
                           (sizeof(HARDERROR_MSG) - sizeof(PORT_MESSAGE));
    Message->h.u2.ZeroInit = 0;
    Message->h.u2.s2.Type = LPC_ERROR_EVENT;
    Message->Status = ErrorStatus & ~HARDERROR_OVERRIDE_ERRORMODE;
    Message->ValidResponseOptions = ValidResponseOptions;
    Message->UnicodeStringParameterMask = UnicodeStringParameterMask;
    Message->NumberOfParameters = NumberOfParameters;
    KeQuerySystemTime(&Message->ErrorTime);

    /* Copy the parameters */
    if (Parameters)
    {
        RtlMoveMemory(&Message->Parameters,
                      Parameters,
                      sizeof(ULONG_PTR) * NumberOfParameters);
    }

    /* Send the LPC Message */
    Status = LpcRequestWaitReplyPort(PortHandle,
                                     (PPORT_MESSAGE)Message,
                                     (PPORT_MESSAGE)Message);
    if (NT_SUCCESS(Status))
    {
        /* Check what kind of response we got */
        if ((Message->Response != ResponseReturnToCaller) &&
            (Message->Response != ResponseNotHandled) &&
            (Message->Response != ResponseAbort) &&
            (Message->Response != ResponseCancel) &&
            (Message->Response != ResponseIgnore) &&
            (Message->Response != ResponseNo) &&
            (Message->Response != ResponseOk) &&
            (Message->Response != ResponseRetry) &&
            (Message->Response != ResponseYes) &&
            (Message->Response != ResponseTryAgain) &&
            (Message->Response != ResponseContinue))
        {
            /* Reset to a default one */
            Message->Response = ResponseReturnToCaller;
        }

        /* Set the response */
        *Response = Message->Response;
    }
    else
    {
        /* Set the response */
        *Response = ResponseReturnToCaller;
    }

    /* Return status */
    return Status;
}

/*++
 * @name ExRaiseAccessViolation
 * @implemented
 *
 * The ExRaiseAccessViolation routine can be used with structured exception
 * handling to throw a driver-determined exception for a memory access
 * violation that occurs when a driver processes I/O requests.
 * See: http://msdn.microsoft.com/library/en-us/Kernel_r/hh/Kernel_r/k102_71b4c053-599c-4a6d-8a59-08aae6bdc534.xml.asp?frame=true (DEAD_LINK)
 *      https://web.archive.org/web/20081231000748/http://www.osronline.com/ddkx/kmarch/k102_814i.htm
 *
 * @return None
 *
 * @remarks None
 *
 *--*/
VOID
NTAPI
ExRaiseAccessViolation(VOID)
{
    /* Raise the Right Status */
    RtlRaiseStatus(STATUS_ACCESS_VIOLATION);
}

/*++
 * @name ExRaiseDatatypeMisalignment
 * @implemented
 *
 * ExRaiseDatatypeMisalignment raises an exception with the exception
 * code set to STATUS_DATATYPE_MISALIGNMENT
 * See: MSDN / DDK
 *      https://web.archive.org/web/20081231000748/http://www.osronline.com/ddkx/kmarch/k102_814i.htm
 *
 * @return None
 *
 * @remarks None
 *
 *--*/
VOID
NTAPI
ExRaiseDatatypeMisalignment(VOID)
{
    /* Raise the Right Status */
    RtlRaiseStatus(STATUS_DATATYPE_MISALIGNMENT);
}

/*++
 * @name ExSystemExceptionFilter
 * @implemented
 *
 * TODO: Add description
 *
 * @return FILLME
 *
 * @remarks None
 *
 *--*/
LONG
NTAPI
ExSystemExceptionFilter(VOID)
{
    return KeGetPreviousMode() != KernelMode ?
           EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH;
}

/*++
 * @name ExRaiseHardError
 * @implemented
 *
 * See NtRaiseHardError and ExpRaiseHardError.
 *
 * @param ErrorStatus
 *        Error Code
 *
 * @param NumberOfParameters
 *        Number of optional parameters in Parameters array
 *
 * @param UnicodeStringParameterMask
 *        Optional string parameter (can be only one per error code)
 *
 * @param Parameters
 *        Array of ULONG parameters for use in error message string
 *
 * @param ValidResponseOptions
 *        See HARDERROR_RESPONSE_OPTION for possible values description
 *
 * @param Response
 *        Pointer to HARDERROR_RESPONSE enumeration
 *
 * @return Status
 *
 *--*/
NTSTATUS
NTAPI
ExRaiseHardError(IN NTSTATUS ErrorStatus,
                 IN ULONG NumberOfParameters,
                 IN ULONG UnicodeStringParameterMask,
                 IN PULONG_PTR Parameters,
                 IN ULONG ValidResponseOptions,
                 OUT PULONG Response)
{
    NTSTATUS Status;
    SIZE_T Size;
    UNICODE_STRING CapturedParams[MAXIMUM_HARDERROR_PARAMETERS];
    ULONG i;
    PVOID UserData = NULL;
    PHARDERROR_USER_PARAMETERS UserParams;
    PWSTR BufferBase;
    ULONG SafeResponse = ResponseNotHandled;

    PAGED_CODE();

    /* Check if we have parameters */
    if (Parameters)
    {
        /* Check if we have strings */
        if (UnicodeStringParameterMask)
        {
            /* Calculate the required size */
            Size = FIELD_OFFSET(HARDERROR_USER_PARAMETERS, Buffer[0]);

            /* Loop each parameter */
            for (i = 0; i < NumberOfParameters; i++)
            {
                /* Check if it's part of the mask */
                if (UnicodeStringParameterMask & (1 << i))
                {
                    /* Copy it */
                    RtlMoveMemory(&CapturedParams[i],
                                  (PVOID)Parameters[i],
                                  sizeof(UNICODE_STRING));

                    /* Increase the size */
                    Size += CapturedParams[i].MaximumLength;
                }
            }

            /* Allocate the user data region */
            Status = ZwAllocateVirtualMemory(NtCurrentProcess(),
                                             &UserData,
                                             0,
                                             &Size,
                                             MEM_COMMIT,
                                             PAGE_READWRITE);
            if (!NT_SUCCESS(Status))
            {
                /* Return failure */
                *Response = ResponseNotHandled;
                return Status;
            }

            /* Set the pointers to our data */
            UserParams = UserData;
            BufferBase = UserParams->Buffer;

            /* Enter SEH block as we are writing to user-mode space */
            _SEH2_TRY
            {
                /* Loop parameters again */
                for (i = 0; i < NumberOfParameters; i++)
                {
                    /* Check if we are in the mask */
                    if (UnicodeStringParameterMask & (1 << i))
                    {
                        /* Update the base */
                        UserParams->Parameters[i] = (ULONG_PTR)&UserParams->Strings[i];

                        /* Copy the string buffer */
                        RtlMoveMemory(BufferBase,
                                      CapturedParams[i].Buffer,
                                      CapturedParams[i].MaximumLength);

                        /* Set buffer */
                        CapturedParams[i].Buffer = BufferBase;

                        /* Copy the string structure */
                        UserParams->Strings[i] = CapturedParams[i];

                        /* Update the pointer */
                        BufferBase += CapturedParams[i].MaximumLength;
                    }
                    else
                    {
                        /* No need to copy any strings */
                        UserParams->Parameters[i] = Parameters[i];
                    }
                }
            }
            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
            {
                /* Return the exception code */
                Status = _SEH2_GetExceptionCode();
                DPRINT1("ExRaiseHardError - Exception when writing data to user-mode, Status 0x%08lx\n", Status);
            }
            _SEH2_END;
        }
        else
        {
            /* Just keep the data as is */
            UserData = Parameters;
        }
    }

    /* Now call the worker function */
    Status = ExpRaiseHardError(ErrorStatus,
                               NumberOfParameters,
                               UnicodeStringParameterMask,
                               UserData,
                               ValidResponseOptions,
                               &SafeResponse);

    /* Check if we had done user-mode allocation */
    if ((UserData) && (UserData != Parameters))
    {
        /* We did! Delete it */
        Size = 0;
        ZwFreeVirtualMemory(NtCurrentProcess(),
                            &UserData,
                            &Size,
                            MEM_RELEASE);
    }

    /* Return status and the response */
    *Response = SafeResponse;
    return Status;
}

/*++
 * @name NtRaiseHardError
 * @implemented
 *
 * This function sends HARDERROR_MSG LPC message to a hard-error listener,
 * typically CSRSS.EXE. See NtSetDefaultHardErrorPort for more information.
 * See also: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtRaiseHardError.html
 *
 * @param ErrorStatus
 *        Error Code
 *
 * @param NumberOfParameters
 *        Number of optional parameters in Parameters array
 *
 * @param UnicodeStringParameterMask
 *        Optional string parameter (can be only one per error code)
 *
 * @param Parameters
 *        Array of ULONG_PTR parameters for use in error message string
 *
 * @param ValidResponseOptions
 *        See HARDERROR_RESPONSE_OPTION for possible values description
 *
 * @param Response
 *        Pointer to HARDERROR_RESPONSE enumeration
 *
 * @return Status
 *
 * @remarks NtRaiseHardError constitutes an easy way to display messages
 *          in GUI without loading any Win32 API libraries.
 *
 *--*/
NTSTATUS
NTAPI
NtRaiseHardError(IN NTSTATUS ErrorStatus,
                 IN ULONG NumberOfParameters,
                 IN ULONG UnicodeStringParameterMask,
                 IN PULONG_PTR Parameters,
                 IN ULONG ValidResponseOptions,
                 OUT PULONG Response)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PULONG_PTR SafeParams = NULL;
    ULONG SafeResponse = ResponseNotHandled;
    UNICODE_STRING SafeString;
    ULONG i;
    ULONG ParamSize = 0;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();

    PAGED_CODE();

    /* Validate parameter count */
    if (NumberOfParameters > MAXIMUM_HARDERROR_PARAMETERS)
    {
        /* Fail */
        return STATUS_INVALID_PARAMETER_2;
    }

    /* Make sure we have some at least */
    if ((Parameters != NULL) && (NumberOfParameters == 0))
    {
        /* Fail */
        return STATUS_INVALID_PARAMETER_2;
    }

    /* Check if we were called from user-mode */
    if (PreviousMode != KernelMode)
    {
        /* First validate the responses */
        switch (ValidResponseOptions)
        {
            /* Check all valid cases */
            case OptionAbortRetryIgnore:
            case OptionOk:
            case OptionOkCancel:
            case OptionRetryCancel:
            case OptionYesNo:
            case OptionYesNoCancel:
            case OptionShutdownSystem:
            case OptionOkNoWait:
            case OptionCancelTryContinue:
                break;

            /* Anything else is invalid */
            default:
                return STATUS_INVALID_PARAMETER_4;
        }

        /* Check if we have parameters */
        if (Parameters)
        {
            /* Calculate size of the parameters */
            ParamSize = sizeof(ULONG_PTR) * NumberOfParameters;

            /* Allocate a safe buffer */
            SafeParams = ExAllocatePoolWithTag(PagedPool, ParamSize, TAG_ERR);
            if (!SafeParams)
            {
                return STATUS_INSUFFICIENT_RESOURCES;
            }
        }

        /* Enter SEH Block */
        _SEH2_TRY
        {
            /* Validate the response pointer */
            ProbeForWriteUlong(Response);

            /* Check if we have parameters */
            if (Parameters)
            {
                /* Validate the parameter pointers */
                ProbeForRead(Parameters, ParamSize, sizeof(ULONG_PTR));

                /* Copy them */
                RtlCopyMemory(SafeParams, Parameters, ParamSize);

                /* Now check if there's strings in it */
                if (UnicodeStringParameterMask)
                {
                    /* Loop every string */
                    for (i = 0; i < NumberOfParameters; i++)
                    {
                        /* Check if this parameter is a string */
                        if (UnicodeStringParameterMask & (1 << i))
                        {
                            /* Probe the structure */
                            ProbeForRead((PVOID)SafeParams[i],
                                         sizeof(UNICODE_STRING),
                                         sizeof(ULONG_PTR));

                            /* Capture it */
                            RtlCopyMemory(&SafeString,
                                          (PVOID)SafeParams[i],
                                          sizeof(UNICODE_STRING));

                            /* Probe the buffer */
                            ProbeForRead(SafeString.Buffer,
                                         SafeString.MaximumLength,
                                         sizeof(UCHAR));
                        }
                    }
                }
            }
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Free captured buffer */
            if (SafeParams) ExFreePoolWithTag(SafeParams, TAG_ERR);

            /* Return the exception code */
            _SEH2_YIELD(return _SEH2_GetExceptionCode());
        }
        _SEH2_END;

        /* Call the system function directly, because we probed */
        Status = ExpRaiseHardError(ErrorStatus,
                                   NumberOfParameters,
                                   UnicodeStringParameterMask,
                                   SafeParams,
                                   ValidResponseOptions,
                                   &SafeResponse);

        /* Free captured buffer */
        if (SafeParams) ExFreePoolWithTag(SafeParams, TAG_ERR);

        /* Enter SEH Block to return the response */
        _SEH2_TRY
        {
            /* Return the response */
            *Response = SafeResponse;
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            /* Get the exception code */
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END;
    }
    else
    {
        /* Reuse variable */
        SafeParams = Parameters;

        /*
         * Call the Executive Function. It will probe
         * and copy pointers to user-mode.
         */
        Status = ExRaiseHardError(ErrorStatus,
                                  NumberOfParameters,
                                  UnicodeStringParameterMask,
                                  SafeParams,
                                  ValidResponseOptions,
                                  &SafeResponse);

        /* Return the response */
        *Response = SafeResponse;
    }

    /* Return status */
    return Status;
}

/*++
 * @name NtSetDefaultHardErrorPort
 * @implemented
 *
 * NtSetDefaultHardErrorPort is typically called only once. After the call,
 * the kernel sets a BOOLEAN flag named ExReadyForErrors to TRUE, and all other
 * attempts to change the default port fail with STATUS_UNSUCCESSFUL error code.
 * See: http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Error/NtSetDefaultHardErrorPort.html
 *      https://web.archive.org/web/20070716133753/http://www.windowsitlibrary.com/Content/356/08/2.html
 *
 * @param PortHandle
 *        Handle to named port object
 *
 * @return Status
 *
 * @remarks Privileges: SE_TCB_PRIVILEGE
 *
 *--*/
NTSTATUS
NTAPI
NtSetDefaultHardErrorPort(IN HANDLE PortHandle)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    /* Check if we have the privileges */
    if (!SeSinglePrivilegeCheck(SeTcbPrivilege, PreviousMode))
    {
        DPRINT1("NtSetDefaultHardErrorPort: Caller requires "
                "the SeTcbPrivilege privilege!\n");
        return STATUS_PRIVILEGE_NOT_HELD;
    }

    /* Only called once during bootup, make sure we weren't called yet */
    if (!ExReadyForErrors)
    {
        /* Reference the hard-error port */
        Status = ObReferenceObjectByHandle(PortHandle,
                                           0,
                                           LpcPortObjectType,
                                           PreviousMode,
                                           (PVOID*)&ExpDefaultErrorPort,
                                           NULL);
        if (NT_SUCCESS(Status))
        {
            /* Keep also a reference to the process handling the hard errors */
            ExpDefaultErrorPortProcess = PsGetCurrentProcess();
            ObReferenceObject(ExpDefaultErrorPortProcess);
            ExReadyForErrors = TRUE;
            Status = STATUS_SUCCESS;
        }
    }

    /* Return status to caller */
    return Status;
}

VOID
__cdecl
_purecall(VOID)
{
    /* Not supported in Kernel Mode */
    RtlRaiseStatus(STATUS_NOT_IMPLEMENTED);
}

/* EOF */