/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    dllinit.c

Abstract:

    This module contains the initialization code for the POSIX Subsystem
    Client DLL.

Author:

    Mark Lucovsky (markl) 27-Jun-1989

Environment:

    User Mode only

Revision History:

    Ellen Aycock-Wright (ellena) 03-Jan-1991
    Converted to DLL initialization routine.

--*/

#include <nt.h>
#include <ntrtl.h>
#include "psxdll.h"

extern void ClientOpen(int);

ULONG_PTR PsxPortMemoryRemoteDelta;
PVOID PsxPortMemoryBase;

BOOLEAN
PsxDllInitialize(
    IN PVOID DllHandle,
    IN ULONG Reason,
    IN PCONTEXT Context OPTIONAL
    )

/*++

Routine Description:

    This function is the DLL initialization routine for the POSIX Emulation
    Subsystem Client DLL.  This function gets control when the applications
    links to this DLL are snapped.

Arguments:

    Context - Supplies an optional context buffer that will be restored
              after all DLL initialization has been completed.  If this
              parameter is NULL then this is a dynamic snap of this module.
              Otherwise this is a static snap prior to the user process
              gaining control.

Return Value:

    False if initialization failed.

--*/
{
   PPEB Peb;
    PPEB_PSX_DATA PebPsxData;
    NTSTATUS Status;

    if (Reason != DLL_PROCESS_ATTACH) {
        return TRUE;
    }

    //
    // Remember our DLL handle in a global variable.
    //

    PsxDllHandle = DllHandle;

    PdxHeap = RtlCreateHeap( HEAP_GROWABLE | HEAP_NO_SERIALIZE,
                             NULL,
                             64 * 1024, // Initial size of heap is 64K
                             4 * 1024,
                             0,
                             NULL
                           );

    if (PdxHeap == NULL) {
        return FALSE;
    }

    Status = PsxInitDirectories();
    if ( !NT_SUCCESS( Status )) {
        return FALSE;
    }

    Status = PsxConnectToServer();
    if (!NT_SUCCESS(Status)) {
        return FALSE;
    }

    Peb = NtCurrentPeb();

    //
    // This is not really an ANSI_STRING but an undocumented data
    // structure.  Read crt32psx\startup\crt0.c for the code that
    // interprets this.
    //


    PsxAnsiCommandLine = *(PANSI_STRING)&(Peb->ProcessParameters->CommandLine);
    if (ARGUMENT_PRESENT(Context)) {
        PebPsxData = (PPEB_PSX_DATA)Peb->SubSystemData;
        PebPsxData->ClientStartAddress = (PVOID)CONTEXT_TO_PROGRAM_COUNTER(Context);
        PROGRAM_COUNTER_TO_CONTEXT(Context, (ULONG_PTR) PdxProcessStartup);
    }

    return TRUE;
}

NTSTATUS
PsxInitDirectories()
{

    PdxDirectoryPrefix.NtCurrentWorkingDirectory.Buffer =
                    RtlAllocateHeap(PdxHeap, 0,2*PATH_MAX);
    PdxDirectoryPrefix.NtCurrentWorkingDirectory.Length = 0;
    PdxDirectoryPrefix.NtCurrentWorkingDirectory.MaximumLength = 2*PATH_MAX;

    PdxDirectoryPrefix.PsxCurrentWorkingDirectory.Buffer =
                    RtlAllocateHeap(PdxHeap, 0,PATH_MAX+1);
    PdxDirectoryPrefix.PsxCurrentWorkingDirectory.Length = 0;
    PdxDirectoryPrefix.PsxCurrentWorkingDirectory.MaximumLength = PATH_MAX+1;

    PdxDirectoryPrefix.PsxRoot.Buffer = RtlAllocateHeap(PdxHeap, 0,2*PATH_MAX);
    PdxDirectoryPrefix.PsxRoot.Length = 0;
    PdxDirectoryPrefix.PsxRoot.MaximumLength = 2*PATH_MAX;

    //
    // Check that memory allocations worked. If not, then bail out
    //

    ASSERT(PdxDirectoryPrefix.NtCurrentWorkingDirectory.Buffer);
    ASSERT(PdxDirectoryPrefix.PsxCurrentWorkingDirectory.Buffer);
    ASSERT(PdxDirectoryPrefix.PsxRoot.Buffer);

    if ( (PdxDirectoryPrefix.NtCurrentWorkingDirectory.Buffer  == NULL) ||
         (PdxDirectoryPrefix.PsxCurrentWorkingDirectory.Buffer == NULL) ||
         (PdxDirectoryPrefix.PsxRoot.Buffer == NULL) ) {

        return ( STATUS_NO_MEMORY );
    }
    return ( STATUS_SUCCESS );
}

NTSTATUS
PsxConnectToServer(VOID)
{
    UNICODE_STRING PsxPortName;
    PSX_API_CONNECTINFO ConnectionInformation;
    ULONG ConnectionInformationLength;
    PULONG AmIBeingDebugged;
    REMOTE_PORT_VIEW ServerView;
    HANDLE PortSection;
    PPEB Peb;
    PPEB_PSX_DATA PebPsxData;
    PORT_VIEW ClientView;
    LARGE_INTEGER SectionSize;
    SECURITY_QUALITY_OF_SERVICE DynamicQos;
    NTSTATUS Status;

    ConnectionInformationLength = sizeof(ConnectionInformation);

    //
    // Create a section to contain the Port Memory.  Port Memory is private
    // memory that is shared between the POSIX client and server processes.
    // This allows data that is too large to fit into an API request message
    // to be passed to the POSIX server.
    //

    SectionSize.LowPart = PSX_CLIENT_PORT_MEMORY_SIZE;
    SectionSize.HighPart = 0;

    // SEC_RESERVE

    Status = NtCreateSection(&PortSection, SECTION_ALL_ACCESS, NULL,
                         &SectionSize, PAGE_READWRITE, SEC_COMMIT, NULL);
    if (!NT_SUCCESS(Status)) {
        KdPrint(("PSXDLL: NtCreateSection: 0x%x\n", Status));
        return Status;
    }

    //
    // Get the Peb address.  Allocate the POSIX subsystem specific portion
    // within the Peb.  This structure will be filled in by the server
    // process as part of the connect logic.
    //

    Peb = NtCurrentPeb();
    Peb->SubSystemData = RtlAllocateHeap(Peb->ProcessHeap, 0,
        sizeof(PEB_PSX_DATA));
    if (! Peb->SubSystemData) {
        NtClose(PortSection);
        return STATUS_NO_MEMORY;
    }

    PebPsxData = (PPEB_PSX_DATA)Peb->SubSystemData;
    PebPsxData->Length = sizeof(PEB_PSX_DATA);

    //
    // Connect to the POSIX Emulation Subsystem server.  This includes a
    // description of the Port Memory section so that the LPC connection
    // logic can make the section visible to both the client and server
    // processes.  Also pass information the POSIX server needs in the
    // connection information structure.
    //

    ClientView.Length = sizeof(ClientView);
    ClientView.SectionHandle = PortSection;
    ClientView.SectionOffset = 0;
    ClientView.ViewSize = SectionSize.LowPart;
    ClientView.ViewBase = 0;
    ClientView.ViewRemoteBase = 0;

    ServerView.Length = sizeof(ServerView);
    ServerView.ViewSize = 0;
    ServerView.ViewBase = 0;

    ConnectionInformation.SignalDeliverer = _PdxSignalDeliverer;
    ConnectionInformation.NullApiCaller = _PdxNullApiCaller;
    ConnectionInformation.DirectoryPrefix = &PdxDirectoryPrefix;
    ConnectionInformation.InitialPebPsxData.Length = PebPsxData->Length;

    //
    // Set up the security quality of service parameters to use over the
    // port.
    //

    DynamicQos.ImpersonationLevel = SecurityImpersonation;
    DynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
    DynamicQos.EffectiveOnly = TRUE;

    PSX_GET_SESSION_OBJECT_NAME(&PsxPortName, PSX_SS_API_PORT_NAME);

    Status = NtConnectPort(&PsxPortHandle, &PsxPortName, &DynamicQos,
               &ClientView, &ServerView, NULL,
               (PVOID)&ConnectionInformation,
               (PULONG)&ConnectionInformationLength);

    NtClose(PortSection);

    if (!NT_SUCCESS(Status)) {
        KdPrint(("PSXDLL: Unable to connect to Posix server: %lx\n",
            Status));
        return Status;
    }

    Status = NtRegisterThreadTerminatePort(PsxPortHandle);
    ASSERT(NT_SUCCESS(Status));

    PsxPortMemoryBase = ClientView.ViewBase;
    PsxPortMemoryRemoteDelta = (ULONG_PTR)ClientView.ViewRemoteBase -
                   (ULONG_PTR)ClientView.ViewBase;

    RtlMoveMemory((PVOID)PebPsxData,
           (PVOID)&ConnectionInformation.InitialPebPsxData,
               PebPsxData->Length);

    PdxPortHeap = RtlCreateHeap( HEAP_NO_SERIALIZE,
                                 ClientView.ViewBase,
                                 ClientView.ViewSize,
                                 ClientView.ViewSize,
                                 0,
                                 0
                               );

    if (PdxPortHeap == NULL) {
        KdPrint(("PsxConnectToServer: RtlCreateHeap failed\n"));
        return STATUS_NO_MEMORY;
    }

    //
    // Connect to the session console port and
    // set the port handle in the PEB.
    //

    Status = PsxInitializeSessionPort(HandleToUlong(PebPsxData->SessionPortHandle));
    if (!NT_SUCCESS(Status)) {
        KdPrint(("PsxConnectToServer: PsxInitSessionPort failed\n"));
        return Status;
    }
    return STATUS_SUCCESS;
}


//
// User mode process entry point.
//

VOID
PdxProcessStartup(
    IN PPEB Peb
    )

{
    PPEB_PSX_DATA PebPsxData;
    PFNPROCESS StartAddress;
    int ReturnCodeFromMain;

    PebPsxData = (PPEB_PSX_DATA)Peb->SubSystemData;
    StartAddress = (PFNPROCESS)(PebPsxData->ClientStartAddress);

    ReturnCodeFromMain = (*StartAddress) (0, NULL);

    _exit(ReturnCodeFromMain);

    NtTerminateProcess(NtCurrentProcess(),STATUS_ACCESS_DENIED);

}


VOID
PdxNullApiCaller(
    IN PCONTEXT Context
    )
{
    PdxNullPosixApi();
#ifdef _X86_
    Context->Eax = 0;
#endif
    NtContinue(Context,FALSE);
    //NOTREACHED
}


VOID
PdxSignalDeliverer (
    IN PCONTEXT Context,
    IN sigset_t PreviousBlockMask,
    IN int Signal,
    IN _handler Handler
    )
{
    (Handler)(Signal);
    sigprocmask(SIG_SETMASK, &PreviousBlockMask, NULL);


#ifdef _X86_
    Context->Eax = 0;
#endif
    NtContinue(Context, FALSE);
    //NOTREACHED
}

VOID
__PdxInitializeData(
    IN int *perrno,
    IN char ***penviron
    )
/*++

Routine Description:

    This function is called from the RTL startup code to notify the DLL of
    the location of the variable 'errno'. Necessary because DLLs cannot
    export data.

Arguments:

    perrno -  Supplies the address of errno - declared in rtl/startup.c

Return Value:
    None.

--*/
{
    Errno = perrno;
    Environ = penviron;
}