/*
** popen.c
** Written by Rick Schaeffer (ricks@isc-br.isc-br.com)
NAME
     popen, pclose - initiate I/O to/from a process

SYNOPSIS
     #include <stdio.h>

     FILE *popen(command, type)
     char *command, *type;

     pclose(stream)
     FILE *stream;

DESCRIPTION
     The arguments to popen are pointers to null-terminated
     strings containing respectively a command line and an
     I/O mode, either "r" for reading or "w" for writing.  It
     creates a pipe between the calling process and the command
     to be executed.  The value returned is a stream pointer that
     can be used (as appropriate) to write to the standard input
     of the command or read from its standard output.

     A stream opened by popen **MUST** be closed by pclose, which
     waits for the associated process to terminate and returns
     the exit status of the command.

     Because stdio files are shared, a type "r" command may be
     used as an input filter, and a type "w" as an output filter.

DIAGNOSTICS
     Popen returns a null pointer if files or processes cannot be
     created.

     Pclose returns -1 if stream is not associated with a
     `popened' command.

*/

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

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

#include <proto/exec.h>
#include <proto/dos.h>

struct POmsg {
	struct Message POm;
	int		rc;
	char	*cmd;
	};


struct pstruct {
	FILE	*fptr;
	struct POmsg	childmsg;
	};

struct pstruct poarray[6];

static struct Process *thistask;

FILE *popen(cmd,mode)
char	*cmd;
char	*mode;
{
	static char tempname[] = "pipe:pXXX.XXX";
	char 		*pname,redir[20],*mktemp();
	short		i;
	int			pmode;
	int			childprocess();
	struct TagItem nptags[] = {
		{NP_Entry,(Tag) childprocess},
		{NP_Input,0},
		{NP_Output,0},
		{NP_CloseInput,0},
		{NP_CloseOutput,0},
		{NP_StackSize,40000},
		{NP_Cli,1},
		{TAG_DONE,0}
		};
	struct pstruct	*poptr;
	BPTR			pfd;
	struct Process	*child;
	struct CommandLineInterface *cli;

	/* First, get pointers to our process and cli structs */
	if (thistask == NULL)
		thistask = (struct Process *) FindTask(NULL);
	cli = Cli();
	poptr = NULL;

	/* now find an open pipe (we currently only allow 6 simultaneously
	   open pipes) */
	for (i=0; i<6; i++) {
		if (poarray[i].fptr == NULL) {
			poptr = &poarray[i];
			break;
			}
		}
	if (poptr == NULL) {
		fprintf(stderr,"popen: Unable to find an open pipe\n");
		return(NULL);
		}
	if (strcmp(mode,"r") == 0)
		pmode = MODE_NEWFILE;
	else if (strcmp(mode,"w") == 0)
		pmode = MODE_OLDFILE;
	else {
		fprintf(stderr,"popen: Mode must be 'r' or 'w'\n");
		return(NULL);
		}

	/* Try to make a guaranteed unique file name for the pipe */
	tempname[5] = 'a' + i;
	strcpy(redir,tempname);
	pname = mktemp(redir);				/* set up a pipe: file name */

	/* Now get the child's stack and priority set up */
	if (thistask->pr_CLI)
		nptags[5].ti_Data = cli->cli_DefaultStack << 2;
	else
		nptags[5].ti_Data = thistask->pr_StackSize;

	/* Open the side of the pipe for the child */
	pfd = Open(pname,pmode);
	if (pfd == 0) {
		fprintf(stderr,"popen: Unable to open pipe file\n");
		return(NULL);
		}

	/* set up the tags for the new process */
	if (pmode == MODE_NEWFILE) {
		nptags[1].ti_Data = (Tag) Input();
		nptags[2].ti_Data = (Tag) pfd;
		nptags[3].ti_Data = FALSE;
		nptags[4].ti_Data = TRUE;
		}
	else {
		nptags[1].ti_Data = (Tag) pfd;
		nptags[2].ti_Data = (Tag) Output();
		nptags[3].ti_Data = TRUE;
		nptags[4].ti_Data = FALSE;
		}

	/* create the command.  since the "System" function runs through
	   the default shell, we need to tell it not to fail so that we
	   ALWAYS get back the exit status.  This wouldn't be necessary
	   if the CLI created by the System function inherited the parent's
	   FAILAT level
	*/
	poptr->childmsg.cmd = malloc(strlen(cmd) + 15);
	strcpy(poptr->childmsg.cmd,"failat 9999\n");
	strcat(poptr->childmsg.cmd,cmd);

	/* Create a port that we can get the child's exit status through */
	poptr->childmsg.POm.mn_ReplyPort = CreatePort(NULL,0);
	poptr->childmsg.POm.mn_Node.ln_Type = NT_MESSAGE;
	poptr->childmsg.POm.mn_Node.ln_Pri = 0;
	if (poptr->childmsg.POm.mn_ReplyPort == 0) {
		fprintf(stderr,"popen: Couldn't create message port\n");
		return(NULL);
		}

	/* Now we can start the new process.  NOTE: this is actually going
	   to create a process consisting ONLY of the function "childprocess"
	   which can be seen below.  childprocess() then runs the command
	   passed in the startup message.
	*/
	child = CreateNewProc(nptags);
	/* now pass the child the startup message */
	PutMsg(&child->pr_MsgPort,(struct Message *) &poptr->childmsg);

	/* Now open our side of the pipe */
	poptr->fptr = fopen(pname,mode);
	if (poptr->fptr == NULL) {
		fprintf(stderr,"popen: Unable to open pipe file %s\n",pname);
		DeletePort(poptr->childmsg.POm.mn_ReplyPort);
		return(NULL);
		}
	return(poptr->fptr);
}

pclose(fptr)
FILE	*fptr;
{
	short		i;

	/* Figure out which pipe we used for this file */
	for (i=0; i<6; i++)
		if (poarray[i].fptr == fptr)
			break;
	if (i > 5) {
		fprintf(stderr,"popen: DISASTER...couldn't find file pointer in pclose\n");
		exit(1);
		}

	/* close the file */
	fclose(fptr);

	/* now wait for the exit status */
	WaitPort(poarray[i].childmsg.POm.mn_ReplyPort);
	poarray[i].fptr = NULL;

	/* clean things up */
	DeletePort(poarray[i].childmsg.POm.mn_ReplyPort);
	free(poarray[i].childmsg.cmd);
	return(poarray[i].childmsg.rc);
}

char *mktemp(template)
char *template;
{
	register char *cp;
	register unsigned long val;

	cp = template;
	cp += strlen(cp);
	for (val = (unsigned long) FindTask(0L) ; ; )
		if (*--cp == 'X') {
			*cp = val%10 + '0';
			val /= 10;
		} else if (*cp != '.')
			break;

	if (*++cp != 0) {
		*cp = 'A';
		while (access(template, 0) == 0) {
			if (*cp == 'Z') {
				*template = 0;
				break;
			}
			++*cp;
		}
	} else {
		if (access(template, 0) == 0)
			*template = 0;
	}
	return template;
}

childprocess()
{
	struct TagItem systags[] = {
		{TAG_DONE,0}
		};
	struct Process	*me;
	struct POmsg	*startupmsg;
	int				i;

	/* find our process structure */
	me = (struct Process *) FindTask(NULL);

	/* Wait for the parent to kick us off */
	WaitPort(&me->pr_MsgPort);

	/* Get the command to execute */
	startupmsg = (struct POmsg *) GetMsg(&me->pr_MsgPort);

	/* Now run the command.  stdin and stdout are already set up */
	i = System(startupmsg->cmd,systags);
	startupmsg->rc = i;
	/* pass the exit code back to the parent */
	ReplyMsg((struct Message *) startupmsg);
	return(0);
}
