/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * os2misc.c
 *
 */

#ifdef MOZ_OS2_HIGH_MEMORY
/* os2safe.h has to be included before os2.h, needed for high mem */
#include <os2safe.h>
#endif

#include <string.h>
#include "primpl.h"

extern int   _CRT_init(void);
extern void  _CRT_term(void);
extern void __ctordtorInit(int flag);
extern void __ctordtorTerm(int flag);

char *
_PR_MD_GET_ENV(const char *name)
{
    return getenv(name);
}

PRIntn
_PR_MD_PUT_ENV(const char *name)
{
    return putenv(name);
}


/*
 **************************************************************************
 **************************************************************************
 **
 **     Date and time routines
 **
 **************************************************************************
 **************************************************************************
 */

#include <sys/timeb.h>
/*
 *-----------------------------------------------------------------------
 *
 * PR_Now --
 *
 *     Returns the current time in microseconds since the epoch.
 *     The epoch is midnight January 1, 1970 GMT.
 *     The implementation is machine dependent.  This is the
 *     implementation for OS/2.
 *     Cf. time_t time(time_t *tp)
 *
 *-----------------------------------------------------------------------
 */

PR_IMPLEMENT(PRTime)
PR_Now(void)
{
    PRInt64 s, ms, ms2us, s2us;
    struct timeb b;

    ftime(&b);
    LL_I2L(ms2us, PR_USEC_PER_MSEC);
    LL_I2L(s2us, PR_USEC_PER_SEC);
    LL_I2L(s, b.time);
    LL_I2L(ms, b.millitm);
    LL_MUL(ms, ms, ms2us);
    LL_MUL(s, s, s2us);
    LL_ADD(s, s, ms);
    return s;
}


/*
 ***********************************************************************
 ***********************************************************************
 *
 * Process creation routines
 *
 ***********************************************************************
 ***********************************************************************
 */

/*
 * Assemble the command line by concatenating the argv array.
 * Special characters intentionally do not get escaped, and it is
 * expected that the caller wraps arguments in quotes if needed
 * (e.g. for filename with spaces).
 *
 * On success, this function returns 0 and the resulting command
 * line is returned in *cmdLine.  On failure, it returns -1.
 */
static int assembleCmdLine(char *const *argv, char **cmdLine)
{
    char *const *arg;
    int cmdLineSize;

    /*
     * Find out how large the command line buffer should be.
     */
    cmdLineSize = 1; /* final null */
    for (arg = argv+1; *arg; arg++) {
        cmdLineSize += strlen(*arg) + 1; /* space in between */
    }
    *cmdLine = PR_MALLOC(cmdLineSize);
    if (*cmdLine == NULL) {
        return -1;
    }

    (*cmdLine)[0] = '\0';

    for (arg = argv+1; *arg; arg++) {
        if (arg > argv +1) {
            strcat(*cmdLine, " ");
        }
        strcat(*cmdLine, *arg);
    }
    return 0;
}

/*
 * Assemble the environment block by concatenating the envp array
 * (preserving the terminating null byte in each array element)
 * and adding a null byte at the end.
 *
 * Returns 0 on success.  The resulting environment block is returned
 * in *envBlock.  Note that if envp is NULL, a NULL pointer is returned
 * in *envBlock.  Returns -1 on failure.
 */
static int assembleEnvBlock(char **envp, char **envBlock)
{
    char *p;
    char *q;
    char **env;
    char *curEnv;
    char *cwdStart, *cwdEnd;
    int envBlockSize;

    PPIB ppib = NULL;
    PTIB ptib = NULL;

    if (envp == NULL) {
        *envBlock = NULL;
        return 0;
    }

    if(DosGetInfoBlocks(&ptib, &ppib) != NO_ERROR) {
        return -1;
    }

    curEnv = ppib->pib_pchenv;

    cwdStart = curEnv;
    while (*cwdStart) {
        if (cwdStart[0] == '=' && cwdStart[1] != '\0'
            && cwdStart[2] == ':' && cwdStart[3] == '=') {
            break;
        }
        cwdStart += strlen(cwdStart) + 1;
    }
    cwdEnd = cwdStart;
    if (*cwdEnd) {
        cwdEnd += strlen(cwdEnd) + 1;
        while (*cwdEnd) {
            if (cwdEnd[0] != '=' || cwdEnd[1] == '\0'
                || cwdEnd[2] != ':' || cwdEnd[3] != '=') {
                break;
            }
            cwdEnd += strlen(cwdEnd) + 1;
        }
    }
    envBlockSize = cwdEnd - cwdStart;

    for (env = envp; *env; env++) {
        envBlockSize += strlen(*env) + 1;
    }
    envBlockSize++;

    p = *envBlock = PR_MALLOC(envBlockSize);
    if (p == NULL) {
        return -1;
    }

    q = cwdStart;
    while (q < cwdEnd) {
        *p++ = *q++;
    }

    for (env = envp; *env; env++) {
        q = *env;
        while (*q) {
            *p++ = *q++;
        }
        *p++ = '\0';
    }
    *p = '\0';
    return 0;
}

/*
 * For qsort.  We sort (case-insensitive) the environment strings
 * before generating the environment block.
 */
static int compare(const void *arg1, const void *arg2)
{
    return stricmp(* (char**)arg1, * (char**)arg2);
}

PRProcess * _PR_CreateOS2Process(
    const char *path,
    char *const *argv,
    char *const *envp,
    const PRProcessAttr *attr)
{
    PRProcess *proc = NULL;
    char *cmdLine = NULL;
    char **newEnvp = NULL;
    char *envBlock = NULL;

    STARTDATA startData = {0};
    APIRET    rc;
    ULONG     ulAppType = 0;
    PID       pid = 0;
    char     *pszComSpec;
    char      pszEXEName[CCHMAXPATH] = "";
    char      pszFormatString[CCHMAXPATH];
    char      pszObjectBuffer[CCHMAXPATH];
    char     *pszFormatResult = NULL;

    /*
     * Variables for DosExecPgm
     */
    char szFailed[CCHMAXPATH];
    char *pszCmdLine = NULL;
    RESULTCODES procInfo;
    HFILE hStdIn  = 0,
          hStdOut = 0,
          hStdErr = 0;
    HFILE hStdInSave  = -1,
          hStdOutSave = -1,
          hStdErrSave = -1;

    proc = PR_NEW(PRProcess);
    if (!proc) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        goto errorExit;
    }

    if (assembleCmdLine(argv, &cmdLine) == -1) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        goto errorExit;
    }

#ifdef MOZ_OS2_HIGH_MEMORY
    /*
     * DosQueryAppType() fails if path (the char* in the first argument) is in
     * high memory. If that is the case, the following moves it to low memory.
     */
    if ((ULONG)path >= 0x20000000) {
        size_t len = strlen(path) + 1;
        char *copy = (char *)alloca(len);
        memcpy(copy, path, len);
        path = copy;
    }
#endif

    if (envp == NULL) {
        newEnvp = NULL;
    } else {
        int i;
        int numEnv = 0;
        while (envp[numEnv]) {
            numEnv++;
        }
        newEnvp = (char **) PR_MALLOC((numEnv+1) * sizeof(char *));
        for (i = 0; i <= numEnv; i++) {
            newEnvp[i] = envp[i];
        }
        qsort((void *) newEnvp, (size_t) numEnv, sizeof(char *), compare);
    }
    if (assembleEnvBlock(newEnvp, &envBlock) == -1) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        goto errorExit;
    }

    rc = DosQueryAppType(path, &ulAppType);
    if (rc != NO_ERROR) {
        char *pszDot = strrchr(path, '.');
        if (pszDot) {
            /* If it is a CMD file, launch the users command processor */
            if (!stricmp(pszDot, ".cmd")) {
                rc = DosScanEnv("COMSPEC", (PSZ *)&pszComSpec);
                if (!rc) {
                    strcpy(pszFormatString, "/C %s %s");
                    strcpy(pszEXEName, pszComSpec);
                    ulAppType = FAPPTYP_WINDOWCOMPAT;
                }
            }
        }
    }
    if (ulAppType == 0) {
        PR_SetError(PR_UNKNOWN_ERROR, 0);
        goto errorExit;
    }

    if ((ulAppType & FAPPTYP_WINDOWAPI) == FAPPTYP_WINDOWAPI) {
        startData.SessionType = SSF_TYPE_PM;
    }
    else if (ulAppType & FAPPTYP_WINDOWCOMPAT) {
        startData.SessionType = SSF_TYPE_WINDOWABLEVIO;
    }
    else {
        startData.SessionType = SSF_TYPE_DEFAULT;
    }

    if (ulAppType & (FAPPTYP_WINDOWSPROT31 | FAPPTYP_WINDOWSPROT | FAPPTYP_WINDOWSREAL))
    {
        strcpy(pszEXEName, "WINOS2.COM");
        startData.SessionType = PROG_31_STDSEAMLESSVDM;
        strcpy(pszFormatString, "/3 %s %s");
    }

    startData.InheritOpt = SSF_INHERTOPT_SHELL;

    if (pszEXEName[0]) {
        pszFormatResult = PR_MALLOC(strlen(pszFormatString)+strlen(path)+strlen(cmdLine));
        sprintf(pszFormatResult, pszFormatString, path, cmdLine);
        startData.PgmInputs = pszFormatResult;
    } else {
        strcpy(pszEXEName, path);
        startData.PgmInputs = cmdLine;
    }
    startData.PgmName = pszEXEName;

    startData.Length = sizeof(startData);
    startData.Related = SSF_RELATED_INDEPENDENT;
    startData.ObjectBuffer = pszObjectBuffer;
    startData.ObjectBuffLen = CCHMAXPATH;
    startData.Environment = envBlock;

    if (attr) {
        /* On OS/2, there is really no way to pass file handles for stdin,
         * stdout, and stderr to a new process.  Instead, we can make it
         * a child process and make the given file handles a copy of our
         * stdin, stdout, and stderr.  The child process then inherits
         * ours, and we set ours back.  Twisted and gross I know. If you
         * know a better way, please use it.
         */
        if (attr->stdinFd) {
            hStdIn = 0;
            DosDupHandle(hStdIn, &hStdInSave);
            DosDupHandle((HFILE) attr->stdinFd->secret->md.osfd, &hStdIn);
        }

        if (attr->stdoutFd) {
            hStdOut = 1;
            DosDupHandle(hStdOut, &hStdOutSave);
            DosDupHandle((HFILE) attr->stdoutFd->secret->md.osfd, &hStdOut);
        }

        if (attr->stderrFd) {
            hStdErr = 2;
            DosDupHandle(hStdErr, &hStdErrSave);
            DosDupHandle((HFILE) attr->stderrFd->secret->md.osfd, &hStdErr);
        }
        /*
         * Build up the Command Line for DosExecPgm
         */
        pszCmdLine = PR_MALLOC(strlen(pszEXEName) +
                               strlen(startData.PgmInputs) + 3);
        sprintf(pszCmdLine, "%s%c%s%c", pszEXEName, '\0',
                startData.PgmInputs, '\0');
        rc = DosExecPgm(szFailed,
                        CCHMAXPATH,
                        EXEC_ASYNCRESULT,
                        pszCmdLine,
                        envBlock,
                        &procInfo,
                        pszEXEName);
        PR_DELETE(pszCmdLine);

        /* Restore our old values.  Hope this works */
        if (hStdInSave != -1) {
            DosDupHandle(hStdInSave, &hStdIn);
            DosClose(hStdInSave);
        }

        if (hStdOutSave != -1) {
            DosDupHandle(hStdOutSave, &hStdOut);
            DosClose(hStdOutSave);
        }

        if (hStdErrSave != -1) {
            DosDupHandle(hStdErrSave, &hStdErr);
            DosClose(hStdErrSave);
        }

        if (rc != NO_ERROR) {
            /* XXX what error code? */
            PR_SetError(PR_UNKNOWN_ERROR, rc);
            goto errorExit;
        }

        proc->md.pid = procInfo.codeTerminate;
    } else {
        /*
         * If no STDIN/STDOUT redirection is not needed, use DosStartSession
         * to create a new, independent session
         */
        rc = DosStartSession(&startData, &ulAppType, &pid);

        if ((rc != NO_ERROR) && (rc != ERROR_SMG_START_IN_BACKGROUND)) {
            PR_SetError(PR_UNKNOWN_ERROR, rc);
            goto errorExit;
        }

        proc->md.pid = pid;
    }

    if (pszFormatResult) {
        PR_DELETE(pszFormatResult);
    }

    PR_DELETE(cmdLine);
    if (newEnvp) {
        PR_DELETE(newEnvp);
    }
    if (envBlock) {
        PR_DELETE(envBlock);
    }
    return proc;

errorExit:
    if (cmdLine) {
        PR_DELETE(cmdLine);
    }
    if (newEnvp) {
        PR_DELETE(newEnvp);
    }
    if (envBlock) {
        PR_DELETE(envBlock);
    }
    if (proc) {
        PR_DELETE(proc);
    }
    return NULL;
}  /* _PR_CreateOS2Process */

PRStatus _PR_DetachOS2Process(PRProcess *process)
{
    /* On OS/2, a process is either created as a child or not.
     * You can't 'detach' it later on.
     */
    PR_DELETE(process);
    return PR_SUCCESS;
}

/*
 * XXX: This will currently only work on a child process.
 */
PRStatus _PR_WaitOS2Process(PRProcess *process,
                            PRInt32 *exitCode)
{
    ULONG ulRetVal;
    RESULTCODES results;
    PID pidEnded = 0;

    ulRetVal = DosWaitChild(DCWA_PROCESS, DCWW_WAIT,
                            &results,
                            &pidEnded, process->md.pid);

    if (ulRetVal != NO_ERROR) {
        printf("\nDosWaitChild rc = %lu\n", ulRetVal);
        PR_SetError(PR_UNKNOWN_ERROR, ulRetVal);
        return PR_FAILURE;
    }
    PR_DELETE(process);
    return PR_SUCCESS;
}

PRStatus _PR_KillOS2Process(PRProcess *process)
{
    ULONG ulRetVal;
    if ((ulRetVal = DosKillProcess(DKP_PROCESS, process->md.pid)) == NO_ERROR) {
        return PR_SUCCESS;
    }
    PR_SetError(PR_UNKNOWN_ERROR, ulRetVal);
    return PR_FAILURE;
}

PRStatus _MD_OS2GetHostName(char *name, PRUint32 namelen)
{
    PRIntn rv;

    rv = gethostname(name, (PRInt32) namelen);
    if (0 == rv) {
        return PR_SUCCESS;
    }
    _PR_MD_MAP_GETHOSTNAME_ERROR(sock_errno());
    return PR_FAILURE;
}

void
_PR_MD_WAKEUP_CPUS( void )
{
    return;
}

/*
 **********************************************************************
 *
 * Memory-mapped files
 *
 * A credible emulation of memory-mapped i/o on OS/2 would require
 * an exception-handler on each thread that might access the mapped
 * memory.  In the Mozilla environment, that would be impractical.
 * Instead, the following simulates those modes which don't modify
 * the mapped file.  It reads the entire mapped file segment at once
 * when MemMap is called, and frees it on MemUnmap.  CreateFileMap
 * only does sanity-checks, while CloseFileMap does almost nothing.
 *
 **********************************************************************
 */

PRStatus _MD_CreateFileMap(PRFileMap *fmap, PRInt64 size)
{
    PRFileInfo64 info;

    /* assert on PR_PROT_READWRITE which modifies the underlying file */
    PR_ASSERT(fmap->prot == PR_PROT_READONLY ||
              fmap->prot == PR_PROT_WRITECOPY);
    if (fmap->prot != PR_PROT_READONLY &&
        fmap->prot != PR_PROT_WRITECOPY) {
        PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
        return PR_FAILURE;
    }
    if (PR_GetOpenFileInfo64(fmap->fd, &info) == PR_FAILURE) {
        return PR_FAILURE;
    }
    /* reject zero-byte mappings & zero-byte files */
    if (!size || !info.size) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }
    /* file size rounded up to the next page boundary */
    fmap->md.maxExtent = (info.size + 0xfff) & ~(0xfff);

    return PR_SUCCESS;
}

PRInt32 _MD_GetMemMapAlignment(void)
{
    /* OS/2 pages are 4k */
    return 0x1000;
}

void * _MD_MemMap(PRFileMap *fmap, PROffset64 offset, PRUint32 len)
{
    PRUint32 rv;
    void *addr;

    /* prevent mappings beyond EOF + remainder of page */
    if (offset + len > fmap->md.maxExtent) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return NULL;
    }
    if (PR_Seek64(fmap->fd, offset, PR_SEEK_SET) == -1) {
        return NULL;
    }
    /* try for high memory, fall back to low memory if hi-mem fails */
    rv = DosAllocMem(&addr, len, OBJ_ANY | PAG_COMMIT | PAG_READ | PAG_WRITE);
    if (rv) {
        rv = DosAllocMem(&addr, len, PAG_COMMIT | PAG_READ | PAG_WRITE);
        if (rv) {
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, rv);
            return NULL;
        }
    }
    if (PR_Read(fmap->fd, addr, len) == -1) {
        DosFreeMem(addr);
        return NULL;
    }
    /* don't permit writes if readonly */
    if (fmap->prot == PR_PROT_READONLY) {
        rv = DosSetMem(addr, len, PAG_READ);
        if (rv) {
            DosFreeMem(addr);
            PR_SetError(PR_UNKNOWN_ERROR, rv);
            return NULL;
        }
    }
    return addr;
}

PRStatus _MD_MemUnmap(void *addr, PRUint32 len)
{
    PRUint32 rv;

    /* we just have to trust that addr & len are those used by MemMap */
    rv = DosFreeMem(addr);
    if (rv) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, rv);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}

PRStatus _MD_CloseFileMap(PRFileMap *fmap)
{
    /* nothing to do except free the PRFileMap struct */
    PR_Free(fmap);
    return PR_SUCCESS;
}

