[alt.sources.amiga] popen/pclose for AmigaDos

rick@rsami.UUCP (Rick Schaeffer) (11/04/90)

This posting contains two implementations of a popen/pclose pair for
AmigaDos.  Just unshar everything and read the popen.notes file for
more info.

The return address from news seems to fail to get email to me.  Please
use the path shown in my signature!  Also, please send followups to
comp.sys.amiga.tech.

-- 
Rick Schaeffer          UUCP:  uunet!isc-br.isc-br.com!ricks
ISC-Bunker Ramo                ricks@isc-br.isc-br.com
Box TAF-C8              Phone: (509)927-5114
Spokane, WA  99220

# This is a shell archive.
# Remove everything above and including the cut line.
# Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
# Run the following text with /bin/sh to create:
#	popen.notes
#	Makefile
#	cat.c
#	pipetst.c
#	popen.c
#	popen2.c
#	tst.c
#
# This archive created:  Sun Nov  4 14:15:48 1990
cat << \SHAR_EOF > popen.notes
Contained in the directory with these notes are sources which produce
two versions of a "popen/pclose" pair for AmigaDos.  The first, "popen.c"
is implemented using the ARP AsyncRun() function and seems to run fine
under either AmigaDos 1.3.2 or AmigaDos 2.0.2.  The second, "popen2.c" is
implemented using the new AmigaDos 2.0 CreateNewProc() and System()
functions and works ONLY on AmigaDos 2.0.2.  Both versions use the standard
AmigaDos PIPE: device and use the process ID to ensure that the pipe name
is unique.

Also included is a program to link with either popen version for testing
purposes and a couple of **real** simple programs to execute through
the pipe to test the functionallity.  See the comments in "pipetst.c",
"tst.c" and "cat.c" for more information.

There is a Makefile included.  You should be able to just type "lmk" and
have it produce the ARP version of everything provided that you have
put the arp headers in your include path.  You can also type "lmk all2"
to have it make the Dos2.0 version ... but you MUST have the 2.0 include
files installed!

I have been using the ARP version of popen in the RCS system for quite
a while and it has worked flawlessly.  (Ray Brand put a slightly different
version of popen into the RCS that was shipped everywhere because he
didn't want to have any dependencies on ARP in the system).  I have
also used it in a couple of other applications and the only problem I
have encountered is that, Under AmigaDos 2.0, the AsyncRun function can't
find the resident programs set up by the Shell (Stack, Path, and other
commands were moved into the shell and are no longer stand-alone commands).
Of course, the 2.0 version hasn't received as much testing but it has
worked with nearly every Dos command I've got.  I would enjoy hearing from
anyone who finds this stuff useful and/or informative.  If you find
any problems or make improvements to this code please let me know about
it!  My various addresses are:

    UseNet:		ricks@isc-br.isc-br.com or uunet!isc-br.isc-br.com!ricks
    bix:		schaef
    Compuserve:		70120,174
    Phone:		(509) 928-3533

    Rick Schaeffer
SHAR_EOF
cat << \SHAR_EOF > Makefile
CC = lc
CFLAGS =

all: cat tst pipetst

all2: cat tst pipetst2

pipetst: pipetst.o popen.o
	blink to pipetst from lib:c.o+pipetst.o+popen.o lib lib:lc.lib SMALLCODE SMALLDATA

pipetst2: pipetst.o popen2.o
	blink to pipetst2 from lib:c.o+pipetst.o+popen2.o lib lib:lc.lib SMALLCODE SMALLDATA

popen2.o: popen2.c
	lc -b0 popen2.c

cat: cat.c
	lc -L cat.c

tst: tst.c
	lc -L tst.c
SHAR_EOF
cat << \SHAR_EOF > cat.c
/*
**  This is a real dumb program written to test the "w" form of popen.
**  it just echoes stdin to stdout and exits with a strange value to
**  see that pclose gets the correct exit status
*/

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

main()
{
	int	ch;

	while ((ch = getchar()) > 0)
		putchar(ch);
	exit(122);
}
SHAR_EOF
cat << \SHAR_EOF > pipetst.c
/*
**  A program to test the popen/pclose pair.  It takes two arguments:
**  the first is either "r" or "w", and the second is a command string.
**  It calls popen with the indicated "r" or "w" mode and passes it the
**  command string.  If the mode was "r", then it reads from the pipe
**  until eof and writes to stdout.  If the mode was "w", then it writes
**  10 simple lines TO the pipe.  In either case, pclose is called and
**  the exit status of the command is displayed.  You should be able to
**  pass nearly any command line for execution...just be sure to enclose
**  the command argument in quotes!  Standard Amiga programs that
**  READ from stdin are kind of rare so use the "r" mode for most
**  things.  Examples:
**		pipetst w cat
**		pipetst r "List c:"
**		pipetst r "Dir sys:"
**		pipetst r "Type pipetst.c"
*/

#include <stdio.h>
#include <fcntl.h>

main(argc,argv)
int	argc;
char	*argv[];
{
	char sline[80];
	FILE *pipein,*pipeout,*popen();
	int		rc;
	short	i;

	if (argc < 2) {
		printf("Need command to run\n");
		exit(1);
		}
	if (argv[1][0] == 'r') {
		pipein = popen(argv[2],"r");
		if (pipein == NULL) {
			printf("Couldn't open pipein file\n");
			exit(1);
			}
		while (fgets(sline,80,pipein) != NULL)
			printf("pipetst: %s",sline);
		rc = pclose(pipein);
		}
	else if (argv[1][0] == 'w') {
		pipeout = popen(argv[2],"w");
		if (pipeout == NULL) {
			printf("Couldn't open pipeout file\n");
			exit(1);
			}
		for (i=0; i<10; i++)
			fprintf(pipeout,"Line %d from pipetst\n",i);
		rc = pclose(pipeout);
		}
	else {
		printf("pipetst needs first parm of r or w\n");
		exit(1);
		}
	printf("pipetst: Return code was %d\n",rc);
}
SHAR_EOF
cat << \SHAR_EOF > popen.c
/*
** 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 <string.h>
#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <arp/arpbase.h>
#include <ios1.h>

struct ArpBase						*ArpBase;
static struct Process				*thistask;
static struct ProcessControlBlock	pcb;

struct pstruct {
	FILE	*fptr;
	struct ZombieMsg	exitmsg;
	};

struct pstruct poarray[6];

FILE *popen(cmd,mode)
char	*cmd;
char	*mode;
{
	char 		*parms;
	static char tempname[] = "pipe:pXXX.XXX";
	char 		*pname,redir[20],*mktemp();
	short		i;
	int			pmode;
	struct pstruct	*poptr;
	BPTR		pfd;

	if (thistask == NULL)
		thistask = (struct Process *) FindTask(NULL);
	if (ArpBase == NULL)
		ArpBase = (struct ArpBase *) OpenLibrary(ArpName,ArpVersion);
	if (ArpBase == NULL) {
		fprintf(stderr,"Arp Open Failed\n");
		return(NULL);
		}
	poptr = NULL;
	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);
		}
	tempname[5] = 'a' + i;
	strcpy(redir,tempname);
	pname = mktemp(redir);				/* set up a pipe: file name */

	setmem(&pcb, sizeof(struct ProcessControlBlock), 0);
	/* Now get the child's stack and priority set up */
	if (thistask->pr_CLI) {
		struct CommandLineInterface		*cli;
		cli = (struct CommandLineInterface *) BADDR(thistask->pr_CLI);
		pcb.pcb_StackSize = cli->cli_DefaultStack << 2;
		}
	else
		pcb.pcb_StackSize = thistask->pr_StackSize;
	pcb.pcb_Pri = thistask->pr_Task.tc_Node.ln_Pri;
	/* 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);
		}
	if (pmode == MODE_NEWFILE)
		pcb.pcb_Output = pfd;
	else
		pcb.pcb_Input = pfd;

	/* Locate the break between command and parameters */
	parms = strpbrk(cmd," \t");
	if (parms) {
		*parms++ = 0;
		parms = stpblk(parms);
		if (parms && *parms == 0)
			parms = NULL;
		}
	/* Create a port for the child's exit message */
	poptr->exitmsg.zm_ExecMessage.mn_ReplyPort = CreatePort(NULL,0);
	if (poptr->exitmsg.zm_ExecMessage.mn_ReplyPort == 0) {
		fprintf(stderr,"popen: Couldn't create message port\n");
		return(NULL);
		}
	pcb.pcb_LastGasp = &poptr->exitmsg;

	if (ASyncRun(cmd,parms,&pcb) < 0) {
		fprintf(stderr,"popen: AsyncRun failed\n");
		DeletePort(poptr->exitmsg.zm_ExecMessage.mn_ReplyPort);
		return(NULL);
		}
	/* 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->exitmsg.zm_ExecMessage.mn_ReplyPort);
		return(NULL);
		}
	return(poptr->fptr);
}

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

	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);
		}
	fclose(fptr);
	WaitPort(poarray[i].exitmsg.zm_ExecMessage.mn_ReplyPort);
	poarray[i].fptr = NULL;
	DeletePort(poarray[i].exitmsg.zm_ExecMessage.mn_ReplyPort);
	return(poarray[i].exitmsg.zm_ReturnCode);
}

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;
}
SHAR_EOF
cat << \SHAR_EOF > popen2.c
/*
** 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);
}
SHAR_EOF
cat << \SHAR_EOF > tst.c
/*
**  This dumb little program simply writes something to stdout and
**  something else to stderr.  It then exits with a strange value.
**  It's only purpose is to test popen() in it's "r" form.
*/

#include <stdio.h>

main()
{
	printf("This went to stdout\n");
	fprintf(stderr,"This went to stderr\n");
	exit(12);	/* test exit code */
}
SHAR_EOF
# End of shell archive
exit 0