/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 * os2misc.c
 *
 */
#include "primpl.h"

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

PR_IMPLEMENT(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.
 * 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;
    char *p, *q;
    int cmdLineSize;
    int numBackslashes;
    int i;
    int argNeedQuotes;

    /*
     * Find out how large the command line buffer should be.
     */
    cmdLineSize = 0;
    for (arg = argv; *arg; arg++) {
        /*
         * \ and " need to be escaped by a \.  In the worst case,
         * every character is a \ or ", so the string of length
         * may double.  If we quote an argument, that needs two ".
         * Finally, we need a space between arguments, a null between
         * the EXE name and the arguments, and 2 nulls at the end 
         * of command line.
         */
        cmdLineSize += 2 * strlen(*arg)  /* \ and " need to be escaped */
                + 2                      /* we quote every argument */
                + 4;                     /* space in between, or final nulls */
    }
    p = *cmdLine = PR_MALLOC(cmdLineSize);
    if (p == NULL) {
        return -1;
    }

    for (arg = argv; *arg; arg++) {
        /* Add a space to separates the arguments */
        if (arg > argv + 1) {
            *p++ = ' '; 
        }
        q = *arg;
        numBackslashes = 0;
        argNeedQuotes = 0;

        /* If the argument contains white space, it needs to be quoted. */
        if (strpbrk(*arg, " \f\n\r\t\v")) {
            argNeedQuotes = 1;
        }

        if (argNeedQuotes) {
            *p++ = '"';
        }
        while (*q) {
            if (*q == '\\') {
                numBackslashes++;
                q++;
            } else if (*q == '"') {
                if (numBackslashes) {
                    /*
                     * Double the backslashes since they are followed
                     * by a quote
                     */
                    for (i = 0; i < 2 * numBackslashes; i++) {
                        *p++ = '\\';
                    }
                    numBackslashes = 0;
                }
                /* To escape the quote */
                *p++ = '\\';
                *p++ = *q++;
            } else {
                if (numBackslashes) {
                    /*
                     * Backslashes are not followed by a quote, so
                     * don't need to double the backslashes.
                     */
                    for (i = 0; i < numBackslashes; i++) {
                        *p++ = '\\';
                    }
                    numBackslashes = 0;
                }
                *p++ = *q++;
            }
        }

        /* Now we are at the end of this argument */
        if (numBackslashes) {
            /*
             * Double the backslashes if we have a quote string
             * delimiter at the end.
             */
            if (argNeedQuotes) {
                numBackslashes *= 2;
            }
            for (i = 0; i < numBackslashes; i++) {
                *p++ = '\\';
            }
        }
        if (argNeedQuotes) {
            *p++ = '"';
        }
        if(arg == argv)
           *p++ = '\0';
    } 

    /* Add 2 nulls at the end */
    *p++ = '\0';
    *p = '\0';
    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)
{
   char szFailed[CCHMAXPATH];
   RESULTCODES procInfo;
   APIRET retVal;
   char *cmdLine = NULL;
   char *envBlock = NULL;
   char **newEnvp;
   PRProcess *proc = NULL;
   HFILE hStdIn  = 0,
           hStdOut = 0, 
           hStdErr = 0;


   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;
   }

   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;
   }

   if (attr) {
       PRBool redirected = PR_FALSE;

      /* 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 = (HFILE) attr->stdinFd->secret->md.osfd;
            DosDupHandle(0, &hStdIn);
        }
        if (attr->stdoutFd) {
            hStdOut = (HFILE) attr->stdoutFd->secret->md.osfd;
            DosDupHandle(1, &hStdOut);
        }
        if (attr->stderrFd) {
            hStdErr = (HFILE) attr->stderrFd->secret->md.osfd;
            DosDupHandle(2, &hStdErr);
        }
   }

   retVal = DosExecPgm(szFailed, 
                       CCHMAXPATH, 
                       EXEC_ASYNCRESULT, 
                       cmdLine, 
                       envBlock, 
                       &procInfo,
                       argv[0]);

   /* Restore our old values.  Hope this works */
   if(hStdIn){
      hStdIn = 0;
      DosDupHandle(0, &hStdIn);      
   }
   if(hStdOut){
      hStdOut = 1;
      DosDupHandle(1, &hStdOut);      
   }
   if(hStdErr){
      hStdErr = 1;
      DosDupHandle(0, &hStdErr);      
   }

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

   proc->md.pid = procInfo.codeTerminate;

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

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

PRStatus _PR_DetachOS2Process(PRProcess *process)
{
    /* This is basically what they did on Windows (CloseHandle)
     * but I don't think it will do much on OS/2. A process is
     * either created as a child or not.  You can't 'detach' it
     * later on.
     */
    DosClose(process->md.pid);
    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 = %i\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;
}

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

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

PR_IMPLEMENT(void)
_PR_MD_WAKEUP_CPUS( void )
{
    return;
}    


/*
 **********************************************************************
 *
 * Memory-mapped files are not supported on OS/2 (or Win16).
 *
 **********************************************************************
 */

PRStatus _MD_CreateFileMap(PRFileMap *fmap, PRInt64 size)
{
    PR_ASSERT(!"Not implemented");
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

void * _MD_MemMap(
    PRFileMap *fmap,
    PRInt64 offset,
    PRUint32 len)
{
    PR_ASSERT(!"Not implemented");
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return NULL;
}

PRStatus _MD_MemUnmap(void *addr, PRUint32 len)
{
    PR_ASSERT(!"Not implemented");
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

PRStatus _MD_CloseFileMap(PRFileMap *fmap)
{
    PR_ASSERT(!"Not implemented");
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

