/* popen.c -- popen/pclose for OS/2. */

/* Set to 0 for distribution.
   Search for "DIAGNOSTIC" in the code to see what it's for. */
#define DIAGNOSTIC 0

#include    <process.h>

#include    <stdio.h>
#include    <stdlib.h>
#include    <sys/types.h>
#include    <sys/stat.h>
#include    <ctype.h>
#include    <string.h>
#include    <fcntl.h>

#include    "config.h"
#include    "os2inc.h"

#define LL_VAL ULONG
#define LL_KEY PID    /* also ULONG, really */

#define STDIN       0
#define STDOUT      1
#define STDERR      2

/* ********************************************************************* *
 *                                                                       *
 *   First, a little linked-list library to help keep track of pipes:    *
 *                                                                       *
 * ********************************************************************* */

/* Map integer PID's onto integer termination codes. */
struct pid_list
{
  HFILE pid;              /* key */
  ULONG term_code;        /* val */
  struct pid_list *next;  /* duh */ 
};

static struct pid_list *pid_ll = (struct pid_list *) NULL;

/* The ll_*() functions all make use of the global var `pid_ll'. */

void
ll_insert (HFILE key, ULONG val)
{
  struct pid_list *new;
  new = (struct pid_list *) xmalloc (sizeof (*new));

  new->pid       = key;
  new->term_code = val;
  new->next      = pid_ll;

  pid_ll = new;
}


void
ll_delete (int key)
{
  struct pid_list *this, *last;

  this = pid_ll;
  last = (struct pid_list *) NULL;

  while (this)
    {
      if (this->pid == key)
        {
          /* Delete this node and leave. */
          if (last)
            last->next = this->next;
          else
            pid_ll = this->next;
          free (this);
          return;
        }

      /* Else no match, so try the next one. */
      last = this;
      this = this->next;
    }
}

ULONG
ll_lookup (HFILE key)
{
  struct pid_list *this = pid_ll;

  while (this)
    {
      if (this->pid == key)
        return this->term_code;

      /* Else no match, so try the next one. */
      this = this->next;
    }

  /* Zero is special in this context anyway. */
  return 0;
}

#if DIAGNOSTIC
ULONG
ll_length ()
{
  struct pid_list *this = pid_ll;
  unsigned long int len;

  for (len = 0; this; len++)
    this = this->next;

  return len;
}

ULONG
ll_print ()
{
  struct pid_list *this = pid_ll;
  unsigned long int i;

  for (i = 0; this; i++)
    {
      printf ("pid_ll[%d] == (%5d --> %5d)\n",
			  i, this->pid, this->term_code);
      this = this->next;
    }

  if (i == 0)
    printf ("No entries.\n");

  return i;
}
#endif /* DIAGNOSTIC */

/* ********************************************************************* *
 *                                                                       *
 *       End of linked-list library, beginning of popen/pclose:          *
 *                                                                       *
 * ********************************************************************* */

/*
 *  Routine: popen
 *  Returns: FILE pointer to pipe.
 *  Action : Exec program connected via pipe, connect a FILE * to the
 *           pipe and return it.
 *  Params : Command - Program to run
 *           Mode    - Mode to open pipe.  "r" implies pipe is connected
 *                     to the programs stdout, "w" connects to stdin.
 */
FILE *
popen (const char *Command, const char *Mode)
{
    HFILE End1, End2, Std, Old1, Old2, Tmp;

    FILE *File;

    char    Fail[256],
            *Args,
            CmdLine[256],
            *CmdExe;

    RESULTCODES
            Result;

    int     Rc;

    if (DosCreatePipe (&End1, &End2, 4096))
        return NULL;

    Std = (*Mode == 'w') ? STDIN : STDOUT ;
    if (Std == STDIN)
    {
        Tmp = End1; End1 = End2; End2 = Tmp;
    }

    Old1 = -1; /* save stdin or stdout */
    DosDupHandle (Std, &Old1);
    DosSetFHState (Old1, OPEN_FLAGS_NOINHERIT);
    Tmp = Std; /* redirect stdin or stdout */
    DosDupHandle (End2, &Tmp);

    if (Std == 1) 
    {
        Old2 = -1; /* save stderr */
        DosDupHandle (STDERR, &Old2);
        DosSetFHState (Old2, OPEN_FLAGS_NOINHERIT);
        Tmp = STDERR;
        DosDupHandle (End2, &Tmp);
    }

    DosClose (End2);
    DosSetFHState (End1, OPEN_FLAGS_NOINHERIT);

    if ((CmdExe = getenv ("COMSPEC")) == NULL )
        CmdExe = "cmd.exe";

    strcpy (CmdLine, CmdExe);
    Args = CmdLine + strlen (CmdLine) + 1; /* skip zero */
    strcpy (Args, "/c ");
    strcat (Args, Command);
    Args[strlen (Args) + 1] = '\0'; /* two zeroes */
    Rc = DosExecPgm (Fail, sizeof (Fail), EXEC_ASYNCRESULT, 
                     (unsigned char *) CmdLine, 0, &Result,
                     (unsigned char *) CmdExe);

    Tmp = Std; /* restore stdin or stdout */
    DosDupHandle (Old1, &Tmp);
    DosClose (Old1);

    if (Std == STDOUT) 
    {
        Tmp = STDERR;   /* restore stderr */
        DosDupHandle (Old2, &Tmp);
        DosClose (Old2);
    }

    if (Rc)
    {
        DosClose (End1);
        return NULL;
    }
  
#ifdef __WATCOMC__
    /* Watcom does not allow mixing operating system handles and
     * C library handles, so we have to convert.
     */
    File = fdopen (_hdopen (End1, *Mode == 'w'? O_WRONLY : O_RDONLY), Mode);
#else
    File = fdopen (End1, Mode);
#endif
    ll_insert ((LL_KEY) End1, (LL_VAL) Result.codeTerminate);

    return File;
}
        

/*
 *  Routine: popenRW
 *  Returns: PID of child process
 *  Action : Exec program connected via pipe, connect int fd's to 
 *           both the stdin and stdout of the process.
 *  Params : Command - Program to run
 *           Pipes   - Array of 2 ints to store the pipe descriptors
 *                     Pipe[0] writes to child's stdin,
 *                     Pipe[1] reads from child's stdout/stderr
 */
int
popenRW (const char **argv, int *pipes)
{
    HFILE Out1, Out2, In1, In2;
    HFILE Old0 = -1, Old1 = -1, Old2 = -1, Tmp;

    int pid;

    if (DosCreatePipe (&Out2, &Out1, 4096))
        return FALSE;

    if (DosCreatePipe (&In1, &In2, 4096))
    {
        DosClose (Out1);
        DosClose (Out2);
        return FALSE;
    }

    /* Save std{in,out,err} */
    DosDupHandle (STDIN, &Old0);
    DosSetFHState (Old1, OPEN_FLAGS_NOINHERIT);
    DosDupHandle (STDOUT, &Old1);
    DosSetFHState (Old2, OPEN_FLAGS_NOINHERIT);
    DosDupHandle (STDERR, &Old2);
    DosSetFHState (Old2, OPEN_FLAGS_NOINHERIT);

    /* Redirect std{in,out,err} */
    Tmp = STDIN;
    DosDupHandle (In1, &Tmp);
    Tmp = STDOUT;
    DosDupHandle (Out1, &Tmp);
    Tmp = STDERR;
    DosDupHandle (Out1, &Tmp);

    /* Close file handles not needed in child */
    
    DosClose (In1);
    DosClose (Out1);
    DosSetFHState (In2, OPEN_FLAGS_NOINHERIT);
    DosSetFHState (Out2, OPEN_FLAGS_NOINHERIT);

    /* Spawn we now our hoary brood. */
    pid = spawnvp (P_NOWAIT, argv[0], argv);

    /* Restore std{in,out,err} */
    Tmp = STDIN;
    DosDupHandle (Old0, &Tmp);
    DosClose (Old0);
    Tmp = STDOUT;
    DosDupHandle (Old1, &Tmp);
    DosClose (Old1);
    Tmp = STDERR;
    DosDupHandle (Old2, &Tmp);
    DosClose (Old2);

    if(pid < 0) 
      {
        DosClose (In2);
        DosClose (Out2);
        return -1;
      }
    
    pipes[0] = In2;
    _setmode (pipes[0], O_BINARY);
    pipes[1] = Out2;
    _setmode (pipes[1], O_BINARY);

    /* Save ID of write-to-child pipe for pclose() */
    ll_insert ((LL_KEY) In2, (LL_VAL) pid);
    
    return pid;
}


/*
 *  Routine: pclose
 *  Returns: TRUE on success
 *  Action : Close a pipe opened with popen();
 *  Params : Pipe - pipe to close
 */
int
pclose (FILE *Pipe) 
{
    RESULTCODES rc;
    PID pid, pid1;
    int Handle = fileno (Pipe);

    fclose (Pipe);

    rc.codeTerminate = -1;

    pid1 = (PID) ll_lookup ((LL_KEY) Handle);
    /* if pid1 is zero, something's seriously wrong */
    if (pid1 != 0)
      {
        DosWaitChild (DCWA_PROCESSTREE, DCWW_WAIT, &rc, &pid, pid1);
        ll_delete ((LL_KEY) Handle);
      }
    return rc.codeTerminate == 0 ? rc.codeResult : -1;
}


#if DIAGNOSTIC
void
main ()
{
  FILE *fp1, *fp2, *fp3;
  int c;

  ll_print ();
  fp1 = popen ("gcc --version", "r");
  ll_print ();
  fp2 = popen ("link386 /?", "r");
  ll_print ();
  fp3 = popen ("dir", "r");
  ll_print ();

  while ((c = getc (fp1)) != EOF)
    printf ("%c", c);

  while ((c = getc (fp2)) != EOF)
    printf ("%c", c);

  while ((c = getc (fp3)) != EOF)
    printf ("%c", c);

  pclose (fp1);
  ll_print ();
  pclose (fp2);
  ll_print ();
  pclose (fp3);
  ll_print ();

  return;
}

#endif /* DIAGNOSTIC */
