/* $Id: dllmain.c 22652 2006-06-27 03:06:26Z ion $
 *
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS system libraries
 * FILE:            lib/kernel32/misc/dllmain.c
 * PURPOSE:         Initialization
 * PROGRAMMER:      Ariadne ( ariadne@xs4all.nl)
 * UPDATE HISTORY:
 *                  Created 01/11/98
 */

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

#include <k32.h>

#define NDEBUG
#include "../include/debug.h"

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

extern UNICODE_STRING SystemDirectory;
extern UNICODE_STRING WindowsDirectory;

HANDLE hProcessHeap = NULL;
HMODULE hCurrentModule = NULL;
HANDLE hBaseDir = NULL;
PPEB Peb;
ULONG SessionId;

static BOOL DllInitialized = FALSE;
static BOOL ConsoleInitialized = FALSE;

BOOL STDCALL
DllMain(HANDLE hInst,
	DWORD dwReason,
	LPVOID lpReserved);

/* Critical section for various kernel32 data structures */
RTL_CRITICAL_SECTION DllLock;
RTL_CRITICAL_SECTION ConsoleLock;

extern BOOL WINAPI DefaultConsoleCtrlHandler(DWORD Event);
extern __declspec(noreturn) VOID CALLBACK ConsoleControlDispatcher(DWORD CodeAndFlag);

extern BOOL FASTCALL NlsInit();
extern VOID FASTCALL NlsUninit();
BOOLEAN InWindows = FALSE;

HANDLE
STDCALL
DuplicateConsoleHandle(HANDLE hConsole,
                       DWORD dwDesiredAccess,
                       BOOL	bInheritHandle,
                       DWORD dwOptions);

#define WIN_OBJ_DIR L"\\Windows"
#define SESSION_DIR L"\\Sessions"

SYSTEM_BASIC_INFORMATION BaseCachedSysInfo;

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

NTSTATUS
WINAPI
OpenBaseDirectory(PHANDLE DirHandle)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING Name = RTL_CONSTANT_STRING(L"\\BaseNamedObjects");
    UNICODE_STRING SymName = RTL_CONSTANT_STRING(L"Local");
    UNICODE_STRING SymName2 = RTL_CONSTANT_STRING(L"Global");
    NTSTATUS Status;
    HANDLE SymHandle;

    InitializeObjectAttributes(&ObjectAttributes,
                               &Name,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);

    Status = NtOpenDirectoryObject(DirHandle,
                                   DIRECTORY_ALL_ACCESS &
                                   ~(DELETE | WRITE_DAC | WRITE_OWNER),
                                   &ObjectAttributes);
    if (!NT_SUCCESS(Status))
    {
        /* FIXME: It's not our job to create the BNO directory, csr does it */
        Status = NtCreateDirectoryObject(DirHandle,
                                         DIRECTORY_ALL_ACCESS,
                                         &ObjectAttributes);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("NtCreateDirectoryObject() failed\n");
        }

        /* Create the "local" Symbolic Link. FIXME: CSR should do this */
        InitializeObjectAttributes(&ObjectAttributes,
                                   &SymName,
                                   OBJ_CASE_INSENSITIVE,
                                   *DirHandle,
                                   NULL);
        Status = NtCreateSymbolicLinkObject(&SymHandle,
                                            SYMBOLIC_LINK_ALL_ACCESS,
                                            &ObjectAttributes,
                                            &Name);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("NtCreateSymbolicLinkObject() failed\n");
        }

        /* Create the "global" Symbolic Link. FIXME: CSR should do this */
        InitializeObjectAttributes(&ObjectAttributes,
                                   &SymName2,
                                   OBJ_CASE_INSENSITIVE,
                                   *DirHandle,
                                   NULL);
        Status = NtCreateSymbolicLinkObject(&SymHandle,
                                            SYMBOLIC_LINK_ALL_ACCESS,
                                            &ObjectAttributes,
                                            &Name);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("NtCreateSymbolicLinkObject() failed\n");
        }
    }

    DPRINT("Opened BNO: %lx\n", *DirHandle);
    return Status;
}

/*
 * @unimplemented
 */
BOOL
WINAPI
BaseQueryModuleData(IN LPSTR ModuleName,
                    IN LPSTR Unknown,
                    IN PVOID Unknown2,
                    IN PVOID Unknown3,
                    IN PVOID Unknown4)
{
    DPRINT1("BaseQueryModuleData called: %s %s %x %x %x\n",
            ModuleName,
            Unknown,
            Unknown2,
            Unknown3,
            Unknown4);
    return FALSE;
}

/*
 * @unimplemented
 */
NTSTATUS
WINAPI
BaseProcessInitPostImport(VOID)
{
    /* FIXME: Initialize TS pointers */
    return STATUS_SUCCESS;
}

BOOL
STDCALL
BasepInitConsole(VOID)
{
    CSR_API_MESSAGE Request;
    ULONG CsrRequest;
    NTSTATUS Status;
    PRTL_USER_PROCESS_PARAMETERS Parameters = NtCurrentPeb()->ProcessParameters;

    WCHAR lpTest[MAX_PATH];
    GetModuleFileNameW(NULL, lpTest, MAX_PATH);
    DPRINT("BasepInitConsole for : %S\n", lpTest);
    DPRINT("Our current console handles are: %lx, %lx, %lx %lx\n", 
           Parameters->ConsoleHandle, Parameters->StandardInput, 
           Parameters->StandardOutput, Parameters->StandardError);

    /* We have nothing to do if this isn't a console app... */
    if (RtlImageNtHeader(GetModuleHandle(NULL))->OptionalHeader.Subsystem !=
        IMAGE_SUBSYSTEM_WINDOWS_CUI)
    {
        DPRINT("Image is not a console application\n");
        Parameters->ConsoleHandle = NULL;
        return TRUE;
    }

    /* Assume one is needed */
    Request.Data.AllocConsoleRequest.ConsoleNeeded = TRUE;

    /* Handle the special flags given to us by BasepInitializeEnvironment */
    if (Parameters->ConsoleHandle == HANDLE_DETACHED_PROCESS)
    {
        /* No console to create */
        DPRINT("No console to create\n");
        Parameters->ConsoleHandle = NULL;
        Request.Data.AllocConsoleRequest.ConsoleNeeded = FALSE;
    }
    else if (Parameters->ConsoleHandle == HANDLE_CREATE_NEW_CONSOLE)
    {
        /* We'll get the real one soon */
        DPRINT("Creating new console\n");
        Parameters->ConsoleHandle = NULL;
    }
    else if (Parameters->ConsoleHandle == HANDLE_CREATE_NO_WINDOW)
    {
        /* We'll get the real one soon */
        DPRINT1("NOT SUPPORTED: HANDLE_CREATE_NO_WINDOW\n");
        Parameters->ConsoleHandle = NULL;
    }
    else
    {
        if (Parameters->ConsoleHandle == INVALID_HANDLE_VALUE)
        {
            Parameters->ConsoleHandle = 0;
        }
        DPRINT("Using existing console: %x\n", Parameters->ConsoleHandle);
    }

    /* Initialize Console Ctrl Handler */
    ConsoleInitialized = TRUE;
    RtlInitializeCriticalSection(&ConsoleLock);
    SetConsoleCtrlHandler(DefaultConsoleCtrlHandler, TRUE);

    /* Now use the proper console handle */
    Request.Data.AllocConsoleRequest.Console = Parameters->ConsoleHandle;

    /*
     * Normally, we should be connecting to the Console CSR Server...
     * but we don't have one yet, so we will instead simply send a create
     * console message to the Base Server. When we finally have a Console
     * Server, this code should be changed to send connection data instead.
     *
     * Also note that this connection should be made for any console app, even
     * in the case above where -we- return.
     */
    CsrRequest = MAKE_CSR_API(ALLOC_CONSOLE, CSR_CONSOLE);
    Request.Data.AllocConsoleRequest.CtrlDispatcher = ConsoleControlDispatcher;
    Status = CsrClientCallServer(&Request, 
                                 NULL,
                                 CsrRequest,
                                 sizeof(CSR_API_MESSAGE));
    if(!NT_SUCCESS(Status) || !NT_SUCCESS(Status = Request.Status))
    {
        DPRINT1("CSR Failed to give us a console\n");
        /* We're lying here, so at least the process can load... */
        return TRUE;
    }

    /* We got the handles, let's set them */
    if ((Parameters->ConsoleHandle = Request.Data.AllocConsoleRequest.Console))
    {
        /* If we already had some, don't use the new ones */
        if (!Parameters->StandardInput)
        {
            Parameters->StandardInput = Request.Data.AllocConsoleRequest.InputHandle;
        }
        if (!Parameters->StandardOutput)
        {
            Parameters->StandardOutput = Request.Data.AllocConsoleRequest.OutputHandle;
        }
        if (!Parameters->StandardError)
        {
            Parameters->StandardError = Request.Data.AllocConsoleRequest.OutputHandle;
        }
    }

    DPRINT("Console setup: %lx, %lx, %lx, %lx\n", 
            Parameters->ConsoleHandle,
            Parameters->StandardInput,
            Parameters->StandardOutput,
            Parameters->StandardError);
    return TRUE;
}


BOOL
STDCALL
DllMain(HANDLE hDll,
        DWORD dwReason,
        LPVOID lpReserved)
{
    NTSTATUS Status;
    BOOLEAN IsServer;
    ULONG Dummy;
    ULONG DummySize = sizeof(Dummy);
    WCHAR SessionDir[256];

    DPRINT("DllMain(hInst %lx, dwReason %lu)\n",
           hDll, dwReason);

    Basep8BitStringToUnicodeString = RtlAnsiStringToUnicodeString;

    /* Cache the PEB and Session ID */
    Peb = NtCurrentPeb();
    SessionId = Peb->SessionId;

    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:

        /* OK, yes, this is really retarded but it works for now */
        InWindows = NtCurrentPeb()->BeingDebugged;

        /*
         * CreateProcess will run in the real kernel32 and it will write
         * its own BaseProcessStartThunk EIP in the CONTEXT that ZwContinue
         * will get. We'll be first called by Ldr while initializing, and we'll
         * be wrapped in 3 layers of SEH, followed by two frames, finally 
         * followed by our CONTEXT on the stack. We'll modify the EIP in it
         * to match the correct one (our own) and then everything works.
         * Tested on XP and 2K3, probably doesn't work in 2K.
         */
        if (InWindows)
        {
            /* 
             * Due to yet another bug in how Windows handles .local, LDR will
             * actually end up loading us twice. The second time will be the
             * "official" load, at a totally different address. It will be,
             * it will be at -that- address that all the APIs will be called.
             * However, that address is dynamic while this one will be static,
             * so we'll do initilization with this one. Plus, at this one,
             * we know exactly that we're within 3 SEH layers.
             */
            if (hDll == (HANDLE)0x7c800000)
            {
                PULONG Eip;
                Eip = (PULONG)*(PULONG)*(PULONG)NtCurrentTeb()->Tib.ExceptionList +
                       0x9 +
                       FIELD_OFFSET(CONTEXT, Eip) / sizeof(ULONG);
                *Eip = (ULONG)BaseProcessStartThunk;
            }
        }

        /* Don't bother us for each thread */
        LdrDisableThreadCalloutsForDll((PVOID)hDll);

        /* Setup the right Object Directory path */
        if (!SessionId)
        {
            /* Use the raw path */
            wcscpy(SessionDir, WIN_OBJ_DIR);
        }
        else
        {
            /* Use the session path */
            swprintf(SessionDir,
                     L"%ws\\%ld%ws",
                     SESSION_DIR,
                     SessionId,
                     WIN_OBJ_DIR);
        }

        /* Connect to the base server */
        DPRINT("Connecting to CSR...\n");
        Status = CsrClientConnectToServer(SessionDir,
                                          InWindows ? 1 : 0,
                                          &Dummy,
                                          &DummySize,
                                          &IsServer);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Failed to connect to CSR (Status %lx)\n", Status);
            ZwTerminateProcess(NtCurrentProcess(), Status);
            return FALSE;
        }

        /* Check if we are running a CSR Server */
        if (!IsServer)
        {
            /* Set the termination port for the thread */
            DPRINT("Creating new thread for CSR\n");
            CsrNewThread();
        }

        hProcessHeap = RtlGetProcessHeap();
        RtlInitializeHandleTable(0xFFFF,
                                 sizeof(BASE_HEAP_HANDLE_ENTRY),
                                 &BaseHeapHandleTable);
        hCurrentModule = hDll;
        DPRINT("Heap: %p\n", hProcessHeap);

        /*
         * Initialize WindowsDirectory and SystemDirectory
         */
        DPRINT("NtSystemRoot: %S\n", SharedUserData->NtSystemRoot);
        RtlCreateUnicodeString (&WindowsDirectory, SharedUserData->NtSystemRoot);
        SystemDirectory.MaximumLength = WindowsDirectory.MaximumLength + 18;
        SystemDirectory.Length = WindowsDirectory.Length + 18;
        SystemDirectory.Buffer = RtlAllocateHeap(hProcessHeap,
                                                 0,
                                                 SystemDirectory.MaximumLength);
        if(SystemDirectory.Buffer == NULL)
        {
           return FALSE;
        }
        wcscpy(SystemDirectory.Buffer, WindowsDirectory.Buffer);
        wcscat(SystemDirectory.Buffer, L"\\System32");

        /* Open object base directory */
        Status = OpenBaseDirectory(&hBaseDir);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Failed to open object base directory (Status %lx)\n", Status);
            return FALSE;
        }

        /* Initialize the DLL critical section */
        RtlInitializeCriticalSection(&DllLock);

        /* Initialize the National Language Support routines */
        if (!NlsInit())
        {
            DPRINT1("NLS Init failed\n");
            return FALSE;
        }

        /* Initialize Console Support */
        if (!BasepInitConsole())
        {
            DPRINT1("Failure to set up console\n");
            return FALSE;
        }

        /* Cache static system information */
        Status = ZwQuerySystemInformation(SystemBasicInformation,
                                          &BaseCachedSysInfo,
                                          sizeof(BaseCachedSysInfo),
                                          NULL);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Failure to get system information\n");
            return FALSE;
        }

        /* Insert more dll attach stuff here! */
        DllInitialized = TRUE;
        DPRINT("Initialization complete\n");
        break;

        case DLL_PROCESS_DETACH:

            DPRINT("DLL_PROCESS_DETACH\n");
            if (DllInitialized == TRUE)
            {
                /* Insert more dll detach stuff here! */
                NlsUninit();

                /* Delete DLL critical section */
                if (ConsoleInitialized == TRUE)
                {
                    RtlDeleteCriticalSection (&ConsoleLock);
                }
                RtlDeleteCriticalSection (&DllLock);

                /* Close object base directory */
                NtClose(hBaseDir);

                RtlFreeUnicodeString (&SystemDirectory);
                RtlFreeUnicodeString (&WindowsDirectory);
            }
            break;

        default:
            break;
    }

    return TRUE;
}

/* EOF */
