/* popen.c
 *
 * Author:  Kai Uwe Rommel <rommel@ars.de>
 * Created: Wed Aug 23 1995
 */
 
static char *rcsid =
"$Id$";
static char *rcsrev = "$Revision$";

/*
 * $Log$ 
 */

#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>

#ifdef __EMX__
#include <alloca.h>
#endif
#ifdef __IBMC__
#define alloca _alloca
#endif
#ifdef __WATCOMC__
#include <malloc.h>
#define tempnam _tempnam
#endif

#include "popen.h"

#if defined(__OS2__) && !defined(OS2)
#define OS2
#endif

#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32)
#define WIN32
#endif

#ifdef M_I86

FILE *popen(const char *cmd, const char *mode)
{
  return (_osmode == DOS_MODE) ? fake_popen(cmd, mode) : _popen(cmd, mode);
}

int pclose(FILE *ptr)
{
  return (_osmode == DOS_MODE) ? fake_pclose(ptr) : _pclose(ptr);
}

int pipe(int *handles)
{
  return _pipe(handles, 4096, O_BINARY);
}

#else

#ifdef OS2

#define INCL_DOS
#define INCL_NOPM
#include <os2.h>

static int pids[MAXPIPES];

FILE *popen(const char *cmd, const char *mode) 
{
  HFILE end1, end2, std, old1, old2, temp;
  FILE *file;
  RESULTCODES res;
  char fail[256], cmdline[256], *args, *shell;
  const char *ptr;
  int fmode, rc;

  for (ptr = mode; *ptr; ptr++)
    switch (*ptr)
    {
    case 'r':
      std = 1;
      break;
    case 'w':
      std = 0;
      break;
    case 't':
      fmode = O_TEXT;
      break;
    case 'b':
      fmode = O_BINARY;
      break;
    }
  
  if (DosCreatePipe(&end1, &end2, 4096))
    return NULL;

  if (std == 0) 
  {
    temp = end1; end1 = end2; end2 = temp;
  }

  old1 = -1; /* save stdin or stdout */
  DosDupHandle(std, &old1);
  DosSetFHState(old1, OPEN_FLAGS_NOINHERIT);
  temp = std; /* redirect stdin or stdout */
  DosDupHandle(end2, &temp);

  if (std == 1) 
  {
    old2 = -1; /* save stderr */
    DosDupHandle(2, &old2);
    DosSetFHState(old2, OPEN_FLAGS_NOINHERIT);
    temp = 2;   /* redirect stderr */
    DosDupHandle(end2, &temp);
  }

  DosClose(end2);
  DosSetFHState(end1, OPEN_FLAGS_NOINHERIT);

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

  strcpy(cmdline, shell);
  args = cmdline + strlen(cmdline) + 1; /* skip zero */
  strcpy(args, "/c ");
  strcat(args, cmd);
  args[strlen(args) + 1] = '\0'; /* two zeroes */

  rc = DosExecPgm(fail, sizeof(fail), EXEC_ASYNCRESULT, cmdline, 0, &res, shell);

  temp = std; /* restore stdin or stdout */
  DosDupHandle(old1, &temp);
  DosClose(old1);

  if (std == 1) 
  {
    temp = 2;   /* restore stderr */
    DosDupHandle(old2, &temp);
    DosClose(old2);
  }

  if (rc) 
  {
    DosClose(end1);
    return NULL;
  }
  
#ifdef __IBMC__
  _setmode(end1, fmode);
#endif

  file = fdopen(end1, mode);
  pids[end1] = res.codeTerminate;

  return file;
}

int pclose(FILE *pipe) 
{
  RESULTCODES rc;
  PID pid;
  int handle = fileno(pipe);

  fclose(pipe);

  if (pids[handle])
    DosWaitChild(DCWA_PROCESSTREE, DCWW_WAIT, &rc, &pid, pids[handle]);

  pids[handle] = 0;

#ifdef __EMX__
  return rc.codeTerminate == 0 ? (rc.codeResult << 8) : -1;
#else
  return rc.codeTerminate == 0 ? rc.codeResult : -1;
#endif
}

int pipe(int *handles)
{
  HFILE end1, end2;

  if (DosCreatePipe(&end1, &end2, 4096))
    return -1;

#ifdef __IBMC__
  _setmode(end1, O_TEXT);
  _setmode(end2, O_TEXT);
#endif

  handles[0] = (int) end1;
  handles[1] = (int) end2;

  return 0;
}

#else

#ifdef WIN32

#include <windows.h>
#ifdef __EMX__
/* #define _open_osfhandle(h, m) _imphandle(h) 
   nolonger needed with RSXNT 1.42 */
#include <mscompat.h>
#endif

PROCESS_INFORMATION pid[MAXPIPES];

FILE *popen(const char *cmd, const char *mode) 
{
  HANDLE rpipe, wpipe, handle, other;
#ifdef VERSION2
  HANDLE old0, old1, old2;
#endif
  SECURITY_ATTRIBUTES security;
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  char buffer[8192];
  const char *ptr;
  int fhandle, fmode, rc;
  OSVERSIONINFO osvi;
  static int WindowsNT = -1;

  if (WindowsNT == -1)
  {
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&osvi);
    WindowsNT = (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT);
  }

  for (fmode = 0, ptr = mode; *ptr; ptr++)
    switch (*ptr)
    {
    case 'r':
      fmode |= O_RDONLY;
      break;
    case 'w':
      fmode |= O_WRONLY;
      break;
    case 't':
      fmode |= O_TEXT;
      break;
    case 'b':
      fmode |= O_BINARY;
      break;
    }
  
  security.nLength = sizeof(security);
  security.bInheritHandle = TRUE;
  security.lpSecurityDescriptor = NULL;

  if (!CreatePipe(&rpipe, &wpipe, &security, 0))
    return NULL;

  handle = (fmode & O_WRONLY) ? wpipe : rpipe;
  other  = (fmode & O_WRONLY) ? rpipe : wpipe;

  SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
  fhandle = _open_osfhandle((long) handle, fmode);

  if (fhandle < 0 || MAXPIPES <= fhandle)
  {
    CloseHandle(rpipe);
    CloseHandle(wpipe);
    return NULL;
  }
  
  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);

  /* Both of the two following versions of redirection work fine. We keep
   * them both here just for reference. The one #ifdef'ed VERSION2 is the
   * more common one but the other one is (fail-) safer.
   */

#ifdef VERSION2
  if (fmode & O_WRONLY) 
  {
    old0 = GetStdHandle(STD_INPUT_HANDLE);
    SetStdHandle(STD_INPUT_HANDLE, rpipe);
  }
  else
  {
    old1 = GetStdHandle(STD_OUTPUT_HANDLE);
    old2 = GetStdHandle(STD_ERROR_HANDLE);
    SetStdHandle(STD_OUTPUT_HANDLE, wpipe);
    SetStdHandle(STD_ERROR_HANDLE, wpipe);
  }
#else
  si.dwFlags = STARTF_USESTDHANDLES;

  if (fmode & O_WRONLY) 
  {
    si.hStdInput  = rpipe;
    si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    si.hStdError  = GetStdHandle(STD_ERROR_HANDLE);
  }
  else
  {
    si.hStdInput  = GetStdHandle(STD_INPUT_HANDLE);
    si.hStdOutput = wpipe;
    si.hStdError  = wpipe;
  }
#endif

  /* Using anonymous pipes and CreateProcess to redirect stdin or stdout does
   * only work without any restrictions on Windows NT. On Windows 95, it
   * does only work if the child process is also a Win32 application and not
   * an old 16-bit executable. The problem is, that the Windows 95 command
   * line interpreter or shell (command.com) is also an old 16-bit program.
   * The semantics of popen() normally require execution through a shell
   * but that can never work on Windows 95 because of this restriction.
   * Therefore, if we do not run on Windows NT, we must try to run the command
   * without a shell to at least cover _some_ cases. Otherwise we just fail.
   */

  if (!WindowsNT)
    rc = CreateProcess(NULL, (char *) cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
  else
  {
    if ((ptr = getenv("COMSPEC")) == NULL)
      ptr = "cmd.exe";

    strcpy(buffer, ptr);
    strcat(buffer, " /c ");
    strcat(buffer, cmd);

    rc = CreateProcess(ptr, buffer, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
  }

#ifdef VERSION2
  if (fmode & O_WRONLY)
    SetStdHandle(STD_INPUT_HANDLE, old0);
  else
  {
    SetStdHandle(STD_OUTPUT_HANDLE, old1);
    SetStdHandle(STD_ERROR_HANDLE, old2);
  }
#endif

  if (rc == 0)
  {
    CloseHandle(rpipe);
    CloseHandle(wpipe);
    return NULL;
  }
  
  CloseHandle(other);

  pid[fhandle] = pi;

  return fdopen(fhandle, (char *) mode);
}

int pclose(FILE *pipe) 
{
  PROCESS_INFORMATION pi;
  DWORD rc;
  int fhandle = fileno(pipe);

  if (fhandle < 0 || MAXPIPES <= fhandle)
    return -1;

  fclose(pipe);

  pi = pid[fhandle];

  WaitForSingleObject(pi.hProcess, INFINITE);
  GetExitCodeProcess(pi.hProcess, &rc);

  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);

#ifdef __EMX__
  return rc << 8;
#else
  return rc;
#endif
}

int pipe(int *handles)
{
  HANDLE rpipe, wpipe;
  SECURITY_ATTRIBUTES security;

  security.nLength = sizeof(security);
  security.bInheritHandle = TRUE;
  security.lpSecurityDescriptor = NULL;

  if (!CreatePipe(&rpipe, &wpipe, &security, 0))
    return -1;

  handles[0] = _open_osfhandle((long) rpipe, O_RDONLY);
  handles[1] = _open_osfhandle((long) wpipe, O_WRONLY);

  return 0;
}

#else

FILE *popen(const char *cmd, const char *mode)
{
  return fake_popen(cmd, mode);
}

int pclose(FILE *pipe)
{
  return fake_pclose(pipe);
}

int pipe(int *handles)
{
  return -1;
}

#endif
#endif
#endif

typedef enum { unopened = 0, reading, writing } pipemode;

static struct
{
  char *name;
  char *cmd;
  pipemode pmode;
}
pipes[MAXPIPES];

FILE *fake_popen(const char *cmd, const char *mode)
{
  FILE *current;
  char *name;
  int cur;
  pipemode curmode;

  if(strchr(mode, 'r') != NULL)
    curmode = reading;
  else if(strchr(mode, 'w') != NULL)
    curmode = writing;
  else
    return NULL;

  if ((name = tempnam(NULL, "pi")) == NULL)
    return NULL;

  if(curmode == reading)
  {
    char *line = alloca(strlen(cmd) + strlen(name) + 4);
    sprintf(line, "%s > %s", cmd, name);
    system(line);

    if((current = fopen(name, mode)) == NULL)
      return NULL;
  }
  else
  {
    if((current = fopen(name, mode)) == NULL)
      return NULL;
  }

  cur = fileno(current);
  pipes[cur].name = name;
  pipes[cur].cmd = strdup(cmd);
  pipes[cur].pmode = curmode;

  return current;
}

int fake_pclose(FILE *pipe)
{
  int cur = fileno(pipe), rval;

  if(pipes[cur].pmode == unopened)
    return -1;

  if(pipes[cur].pmode == reading)
  {
    rval = fclose(pipe);
    unlink(pipes[cur].name);
  }
  else
  {
    char *line = alloca(strlen(pipes[cur].cmd) + strlen(pipes[cur].name) + 4);
    fclose(pipe);
    sprintf(line, "%s < %s", pipes[cur].cmd, pipes[cur].name);
    rval = system(line);
    unlink(pipes[cur].name);
  }

  free(pipes[cur].name);
  free(pipes[cur].cmd);
  pipes[cur].pmode = unopened;

  return rval;
}

#ifdef TEST

#include <conio.h>

int main(void)
{
  FILE *pipe;
  int x = 0;
  char line[256];

  pipe = popen("diff.exe", "r");

  while (fgets(line, sizeof(line), pipe))
  {
    fputs(line, stdout);
    x++;
  }

  printf("%d lines, rc = %d\n", x, pclose(pipe));
  fflush(stdout);
  getch();

  pipe = popen("more.com", "w");

  for (x = 0; x < 32; x++)
    fprintf(pipe, "line %d\n", x);

  printf("rc = %d\n", pclose(pipe));

  return 0;
}

#endif

/* end of popen.c */
