/* $Id: amiga.c,v 1.1.1.2 1998/04/15 19:21:59 lhecking Exp $ */

/* GNUPLOT - amiga.c */

/*[
 * Copyright 1986 - 1993, 1998   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * amiga.c
 *
 * Written by Carsten Steger <stegerc@informatik.tu-muenchen.de>
 *
 * Popen and pclose have the same semantics as their UNIX counterparts.
 *
 * Additionally, they install an exit trap that closes all open pipes,
 * should the program terminate abnormally.
 */


#include <stdio.h>
#include <ios1.h>
#include <error.h>
#include <string.h>
#include <stdlib.h>

#include <exec/types.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>

#ifdef PIPES  /* dont bother if pipes are not being used elsewhere */

/* Maximum number of open pipes. If this is set to a number > 10, the code
 * that constructs the pipe names in popen () will have to be modified.
 */
#define MAX_OPEN_PIPES 10

/* We need at least this Dos version to work. */
#define DOS_VERSION 37


/* This data structure is sent to the child process with sm_Cmd set to the
 * command to be executed. When the child is done it sets sm_RetCode to
 * the return code of the executed command.
 */
struct StartupMessage {
  struct Message sm_Msg;
  LONG sm_RetCode;
  UBYTE *sm_Cmd;
};

/* We keep track of the open pipe through this data structure. */
struct PipeFileDescriptor {
  FILE *pfd_File;
  struct StartupMessage pfd_Msg;
};


/* Needed to check for the required Dos version. */
extern struct DosLibrary *DOSBase;

/* This data structure keeps track of the pipes that are still open. */
static struct PipeFileDescriptor OpenPipes[MAX_OPEN_PIPES];

/* The address of the process that calls popen or pclose. */
static struct Process *ThisProcess;

/* Are we called for the first time? */
static LONG FirstCall = TRUE;


/* Prototypes for the functions below. */
FILE *popen (const char *command, const char *mode);
int pclose (FILE *stream);
static void CleanUpPipes (void);
static int __saveds ChildEntry (void);


FILE *popen (command, mode)
const char *command;
const char *mode;
{
  UBYTE PipeName[16];
  ULONG ProcAddress;
  UBYTE HexDigit;
  UBYTE *NextChar;
  struct CommandLineInterface *ThisCli;
  struct PipeFileDescriptor *PipeToUse;
  LONG PipeNumToUse;
  LONG ChildPipeMode;
  BPTR ChildPipe;
  FILE *ParentPipe;
  struct Process *ChildProcess;
  struct TagItem NewProcTags[8] = {
    {NP_Entry, (Tag) ChildEntry},
    {NP_Cli, TRUE},
    {NP_StackSize, 4096},
    {NP_Input, NULL},
    {NP_Output, NULL},
    {NP_CloseInput, FALSE},
    {NP_CloseOutput, FALSE},
    {TAG_DONE, 0}
  };

  /* Test whether we're using the right Dos version. */
  if (DOSBase->dl_lib.lib_Version < DOS_VERSION) {
    errno = EPIPE;
    return NULL;
  }

  /* If we're called for the first time, install exit trap and do some
   * initialisation stuff.
   */
  if (FirstCall) {
    /* Initialise pipe file descriptor table. */
    memset (OpenPipes, 0, sizeof (OpenPipes));
    
    /* Install our exit trap. */
    if (atexit (CleanUpPipes) != 0) {
      errno = EPIPE;
      return NULL;
    }
    FirstCall = FALSE;
  }

  /* If we don't know our process' address yet, we should get it now. */
  if (ThisProcess == NULL)
    ThisProcess = (struct Process *) FindTask (NULL);

  /* Get our Cli structure. */
  ThisCli = Cli ();

  /* Now try to find an empty slot in the pipe file descriptor table.
   * Return NULL if no slot is available.
   */
  for (PipeNumToUse = 0; PipeNumToUse < MAX_OPEN_PIPES; PipeNumToUse++)
    if (OpenPipes[PipeNumToUse].pfd_File == NULL) break;
  if (PipeNumToUse >= MAX_OPEN_PIPES) {
    errno = EMFILE;
    return NULL;
  }
  PipeToUse = &OpenPipes[PipeNumToUse];

  /* Check if the specified mode is valid. */
  if (strcmp (mode, "r") == 0)
    ChildPipeMode = MODE_NEWFILE;
  else if (strcmp (mode, "w") == 0)
    ChildPipeMode = MODE_OLDFILE;
  else {
    errno = EINVAL;
    return NULL;
  }

  /* Make a unique file name for the pipe that we are about to open. The
   * file name has the following format: "PIPE:XXXXXXXX_Y", where
   * XXXXXXXX is the address of our process in hex, Y is the number of the
   * slot in the pipe descriptor table that we will use. The code is
   * equivalent to
   * sprintf (PipeNameWriter, "PIPE:%08lX_%1d", ThisProcess, PipeNumToUse);
   * but it doesn't need sprintf and therefore makes programs that don't
   * use printf a lot shorter.
   */
  strcpy (PipeName, "PIPE:00000000_0");
  NextChar = PipeName + 12;
  ProcAddress = (ULONG) ThisProcess;
  while (ProcAddress != 0) {
    HexDigit = (UBYTE) ProcAddress & 0xf;
    HexDigit = HexDigit < 10 ? HexDigit + '0' : HexDigit - 10 + 'A';
    *NextChar-- = HexDigit;
    ProcAddress >>= 4;
  }
  /* If MAX_OPEN_PIPES > 10, this will have to be modified. */
  PipeName[14] = ((UBYTE) PipeNumToUse) + '0';

  /* Create tags for the child process. */
  if (ThisProcess->pr_CLI)
    NewProcTags[2].ti_Data = ThisCli->cli_DefaultStack << 2;
  else
    NewProcTags[2].ti_Data = ThisProcess->pr_StackSize;

  /* Open both ends of the pipe. The child's side is opened with Open (),
   * while the parent's side is opened with fopen ().
   */
  ChildPipe = Open (PipeName, ChildPipeMode);
  ParentPipe = fopen (PipeName, mode);
  if (ChildPipeMode == MODE_NEWFILE) {
    NewProcTags[3].ti_Data = Input ();
    NewProcTags[4].ti_Data = ChildPipe;
    NewProcTags[5].ti_Data = FALSE;
    NewProcTags[6].ti_Data = TRUE;
  } else {
    NewProcTags[3].ti_Data = ChildPipe;
    NewProcTags[4].ti_Data = Output ();
    NewProcTags[5].ti_Data = TRUE;
    NewProcTags[6].ti_Data = FALSE;
  }
  if (ChildPipe == NULL || ParentPipe == NULL) {
    errno = EPIPE;
    goto cleanup;
  }

  /* Now generate a entry in the pipe file descriptor table. */
  PipeToUse->pfd_Msg.sm_Cmd = malloc (strlen (command) + 1);
  if (PipeToUse->pfd_Msg.sm_Cmd == NULL) {
    errno = ENOMEM;
    goto cleanup;
  }
  strcpy (PipeToUse->pfd_Msg.sm_Cmd, command);
  PipeToUse->pfd_Msg.sm_Msg.mn_ReplyPort = CreateMsgPort ();
  if (PipeToUse->pfd_Msg.sm_Msg.mn_ReplyPort == NULL) {
    errno = ENOMEM;
    goto cleanup;
  }
  PipeToUse->pfd_Msg.sm_Msg.mn_Node.ln_Type = NT_MESSAGE;
  PipeToUse->pfd_Msg.sm_Msg.mn_Node.ln_Pri = 0;
  PipeToUse->pfd_Msg.sm_Msg.mn_Length = sizeof (struct StartupMessage);
  PipeToUse->pfd_File = ParentPipe;

  /* Now create the child process. */
  ChildProcess = CreateNewProc (NewProcTags);
  if (ChildProcess == NULL) {
    errno = ENOMEM;
    goto cleanup;
  }

  /* Pass the startup message to the child process. */
  PutMsg (&ChildProcess->pr_MsgPort, (struct Message *) &PipeToUse->pfd_Msg);

  /* This is the normal exit point for the function. */
  return ParentPipe;

  /* This code is only executed if there was an error. In this case the
   * allocated resources must be freed. The code is actually clearer (at
   * least in my opinion) and more concise by using goto than by using a
   * function (global variables or function parameters needed) or a lot
   * of if-constructions (code gets blown up unnecessarily).
   */
cleanup:
  if (PipeToUse->pfd_Msg.sm_Msg.mn_ReplyPort == NULL)
    DeleteMsgPort (PipeToUse->pfd_Msg.sm_Msg.mn_ReplyPort);
  if (ParentPipe)
    fclose (ParentPipe);
  if (ChildPipe)
    Close (ChildPipe);
  return NULL;
}


int pclose (stream)
FILE *stream;
{
  LONG PipeToClose;

  /* Test whether we're using the right Dos version. */
  if (DOSBase->dl_lib.lib_Version < DOS_VERSION) {
    errno = EPIPE;
    return -1;
  }

  /* Test whether this is the first call to this module or not. If so,
   * pclose has been called before popen and we return with an error
   * because the initialisation has yet to be done.
   */
  if (FirstCall) {
    errno = EBADF;
    return -1;
  }

  /* Search for the correct table entry and close the associated file. */
  for (PipeToClose = 0; PipeToClose < MAX_OPEN_PIPES; PipeToClose++)
    if (OpenPipes[PipeToClose].pfd_File == stream) break;
  if (PipeToClose >= MAX_OPEN_PIPES) {
    errno = EBADF;
    return -1;
  }
  fclose (stream);

  /* Now wait for the child to terminate and get its exit status. */
  WaitPort (OpenPipes[PipeToClose].pfd_Msg.sm_Msg.mn_ReplyPort);
  OpenPipes[PipeToClose].pfd_File = NULL;

  /* Free the allocates resources. */
  DeleteMsgPort (OpenPipes[PipeToClose].pfd_Msg.sm_Msg.mn_ReplyPort);
  free (OpenPipes[PipeToClose].pfd_Msg.sm_Cmd);

  return OpenPipes[PipeToClose].pfd_Msg.sm_RetCode;
}


static void CleanUpPipes ()
{
  LONG Count;
  FILE *Pipe;

  /* Close each open pipe. */
  for (Count = 0; Count < MAX_OPEN_PIPES; Count++) {
    Pipe = OpenPipes[Count].pfd_File;
    if (Pipe != NULL)
      pclose (Pipe);
  }
}


static int __saveds ChildEntry ()
{
  struct Process *ChildProc;
  struct StartupMessage *StartUpMessage;
  LONG ReturnCode;
  struct DosLibrary *DOSBase;
  struct TagItem SysTags[3] = {
    {SYS_Asynch, FALSE},
    {SYS_UserShell, TRUE},
    {TAG_DONE, 0}
  };

  /* We need to open this library, because we don't inherit it from our
   * parent process.
   */
  DOSBase = (struct DosLibrary *) OpenLibrary ("dos.library", DOS_VERSION);

  /* Get the childs process structure. */
  ChildProc = (struct Process *) FindTask (NULL);

  /* Wait for the startup message from the parent. */
  WaitPort (&ChildProc->pr_MsgPort);
  StartUpMessage = (struct StartupMessage *) GetMsg (&ChildProc->pr_MsgPort);

  /* Now run the command and return the result. */
  if (DOSBase != NULL)
    ReturnCode = System (StartUpMessage->sm_Cmd, SysTags);
  else
    ReturnCode = 10000;
  StartUpMessage->sm_RetCode = ReturnCode;

  /* Tell the parent that we are done. */
  ReplyMsg ((struct Message *) StartUpMessage);

  if (DOSBase)
    CloseLibrary ((struct Library *) DOSBase);

  return 0;
}

#endif /* PIPES */
