[comp.sys.amiga.tech] popen/pclose for 1.3

mab@druwy.ATT.COM (Alan Bland) (01/17/89)

Does anyone have a UNIX-like implementation of popen and pclose that
uses the 1.3 pipe handler?  I'm porting some UNIX code and would like
to take advantage of pipes.  I could roll my own, but I haven't yet
learned all the secrets of file handles with Execute(), and figure
there must be somebody out there who has already done popen and pclose
and taken care of most or all of the gotchas.  Thanx.
-- 
// Alan Bland
// AT&T Bell Laboratories, Denver CO
// (303)-538-3510 - att!druwy!mab

nordmark@nada.kth.se (Arne Nordmark) (01/19/89)

In article <3768@druwy.ATT.COM> mab@druwy.ATT.COM (Alan Bland) writes:
>
>Does anyone have a UNIX-like implementation of popen and pclose that
>uses the 1.3 pipe handler?  I'm porting some UNIX code and would like
>to take advantage of pipes.  I could roll my own, but I haven't yet
>learned all the secrets of file handles with Execute(), and figure
>there must be somebody out there who has already done popen and pclose
>and taken care of most or all of the gotchas.  Thanx.

Yeah, there is a *lot* of gotchas. This took me at least a week of
headaches to put together last spring, but it emulates the *IX popen()
very well. (It does not run the popened command through sh though :-)

I've used it in lots of *IX stuff that I've ported. (including UUCP).

1.3 didn't exist when I wrote this, so I used the ConMan PIP: device
instead.
Later I have tried to use the 1.3 pipe device, but to no avail - PIPE:
doesn't seem to work like a *IX pipe, but PIP: certainly does.

Also I have used ARP's ASyncRun() instead of Execute(). The latter is
a truly miserable function that doesn't at all live up to the requirements
for writing a popen() function. (I have tried...)

This isn't a nice peice of code, but it works! (it has been extensively
tested). Feel free to modify and/or clean up the code, and of course use it!

Have fun!

  -- Gunnar

/****************************************************************************
 *
 *  popen.c V1.0 (c) CopyRight 1988, Gunnar Nordmark.
 *  
 *  Gunnar Nordmark          gno@stacken.kth.se
 *  Nora strand 5            gno@SESTAK.BITNET
 *  S-182 34 DANDERYD        {mcvax,munnari,ukc,unido}!enea!sics!epsilon!gno
 *  SWEDEN
 *
 *  You may freely distribute this source as long as
 *  the Copyright notice is left intact.
 *
 *  Notes:
 *  Works for Lattice 4.01   (don't forget to compile with -y)
 *    lc -v -y popen
 *  or Manx 3.6a
 *    cc popen
 *
 *  Needs arp.library V34 or greater. DON'T FORGET TO OPEN IT!
 *  Also needs PIP: device included in ConMan 1.1 (must be in your MountList)
 *
 *  Important:
 *  If you haven't got the bug-fix for arpbase.h 34.00 you must change the
 *  structure definition for ProcessControlBlock to the following:
 *
struct ProcessControlBlock {
	ULONG	pcb_StackSize;
	BYTE	pcb_Pri;
	BYTE	pcb_Control;
	APTR	pcb_TrapCode;
	ULONG	pcb_Input,p_Output;
	union {
		ULONG	pcb_SplatFile;
		BYTE	*pcb_ConName;
	} pcb_Console;
	CPTR	pcb_LoadedCode;
	struct ZombieMsg *pcb_LastGasp;
	struct MsgPort	*pcb_WBProcess;
};
 *
 ****************************************************************************

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.

BUGS
     Buffered reading before opening an input filter may leave
     the standard input of that filter mispositioned.  Similar
     problems with an output filter may be forestalled by careful
     buffer flushing, for instance, with fflush.

 ***************************************************************************/

#include <libraries/dosextens.h>

/* fix for arpbase.h, do this *before* including arpbase.h */
#define p_Output pcb_Output

#include <libraries/arpbase.h>
#include <arpfunctions.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>

#ifdef AZTEC_C

#define READONLY	O_RDONLY
#define WRITEONLY	O_WRONLY

extern char *AllocMem();
extern struct Task *FindTask();
extern struct Message *GetMsg();
extern struct MsgPort *CreatePort();
extern BPTR Open();
extern LONG Read();
extern LONG Write();
extern FILE *fdopen();
extern char *calloc();

#else

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

#define READONLY	UFB_RA | O_RAW
#define WRITEONLY	UFB_RA | O_RAW
#define MAXSTREAM	NUFBS
#define _devtab		_ufbs
#define fd		ufbfh
#define mode		ufbflg
#define geta4(a)

extern struct UFB _ufbs[];

#endif

#define PIPE		"PIP:2000"
#define ACTION_DOUBLE	2000L
#define ACTION_END	1007L
#define STKSIZ		1000
#define LINSIZ		256
#define YES		1
#define NO		0

#define iswspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')


struct closeinfo {
  struct MsgPort *replyport;
  BPTR pipe2;
};


struct MsgPort *replyports[MAXSTREAM];


/*
 * openpipe()
 *		Opens a pipe and returns two filehandles to it.
 *		The address of the second handle must be passed
 *		as an argument. Both results are NULL if the
 *		opens fails.
 */

BPTR openpipe(pipe2)
BPTR *pipe2;
{
  BPTR pipe1;
  struct FileHandle *handle1, *handle2;


  *pipe2=NULL;

  if (!(handle2=(struct FileHandle *)
	AllocMem((LONG)sizeof(struct FileHandle), MEMF_PUBLIC)))
    return NULL;

  if (!(pipe1=Open(PIPE, ACTION_DOUBLE))) {
    FreeMem((BYTE *)handle2, (LONG)sizeof(struct FileHandle));
    return NULL;
  }

  handle1=(struct FileHandle *)BADDR(pipe1);
  *handle2=*handle1;
  *pipe2=(long)handle2>>2;

  return pipe1;
}


/*
 * closepipe()
 *		Closes the 'second' filehandle returned by openpipe()
 *		The 'first' one you should Close() normally;
 */

void closepipe(pipe2)
BPTR pipe2;
{
  struct FileHandle *handle2;


  handle2=(struct FileHandle *)BADDR(pipe2);
  SendPacket(ACTION_END, (LONG *)&handle2->fh_Arg1, handle2->fh_Type);
  FreeMem((BYTE *)handle2, (LONG)sizeof(struct FileHandle));
}


void closeproc()
{
  struct Process *proc;
  struct ZombieMsg *zmsg;
  struct closeinfo *ci;


  geta4();
  proc=(struct Process *)FindTask(NULL);
  WaitPort(&proc->pr_MsgPort);
  zmsg=(struct ZombieMsg *)GetMsg(&proc->pr_MsgPort);
  ci=(struct closeinfo *)zmsg->zm_UserInfo;
  closepipe(ci->pipe2);
  PutMsg(ci->replyport, (struct Message *)zmsg);
}


FILE *popen(commandline, type)
char *commandline, *type;
{
  char commandbuf[LINSIZ], *command=commandbuf, *arguments;
  struct ProcessControlBlock pcb;
  int fh;
  FILE *stream=NULL;
  BPTR input, output, errput;
  BPTR pipe1=NULL;
  BPTR pipe2=NULL;
  struct MsgPort *replyport=NULL;
  struct MsgPort *port=NULL;
  struct ZombieMsg *zmsg=NULL;
  struct closeinfo *ci=NULL;
  struct Process *proc;
  

  if (strlen(commandline)>=LINSIZ || strcmp(type, "r") && strcmp(type, "w"))
    goto popenerror;

  strcpy(command, commandline);
  while (iswspace(*command))
    command++;
  arguments=command;
  while (*arguments && !iswspace(*arguments))
    arguments++;
  if (*arguments)
    *arguments++='\0';
  else
    *++arguments='\0';
  strcat(arguments, "\n");

  input=_devtab[fileno(stdin)].fd;
  output=_devtab[fileno(stdout)].fd;
  errput=_devtab[fileno(stderr)].fd;

  if (!(pipe1=openpipe(&pipe2)))
    goto popenerror;

  for (fh=3; fh<=MAXSTREAM; fh++)
    if (_devtab[fh].fd==NULL)
      break;
  if (fh>MAXSTREAM)
    goto popenerror;

  _devtab[fh].fd=pipe1;
  _devtab[fh].mode=*type=='r' ? READONLY : WRITEONLY;

  if (!(stream=fdopen(fh, type)))
    goto popenerror;

  if (!(ci=(struct closeinfo *)calloc(1, sizeof(struct closeinfo))))
    goto popenerror;

  if (!(zmsg=(struct ZombieMsg *)calloc(1, sizeof(struct ZombieMsg))))
    goto popenerror;

  if ((replyport=CreatePort(NULL, 0L))==NULL)
    goto popenerror;

  setmem(&pcb, sizeof(struct ProcessControlBlock), 0);

  pcb.pcb_StackSize=STKSIZ;
  pcb.pcb_Control=PRF_SAVEIO | PRF_NOCLI | PRF_CODE;
  pcb.pcb_Input=input;
  pcb.pcb_Output=output;
  pcb.pcb_LoadedCode=(CPTR)closeproc;

  if (ASyncRun("closeproc", NULL, &pcb)!=0L)
    goto popenerror;

  port=pcb.pcb_WBProcess;

  ci->replyport=replyport;
  ci->pipe2=pipe2;

  zmsg->zm_ExecMessage.mn_Node.ln_Type=NT_MESSAGE;
  zmsg->zm_ExecMessage.mn_ReplyPort=port;
  zmsg->zm_UserInfo=(ULONG)ci;

  proc=(struct Process *)FindTask(NULL);

  setmem(&pcb, sizeof(struct ProcessControlBlock), 0);

  pcb.pcb_StackSize=proc->pr_CLI ?
    ((struct CommandLineInterface *)BADDR(proc->pr_CLI))->cli_DefaultStack<<2 :
    proc->pr_StackSize;
  pcb.pcb_Pri=proc->pr_Task.tc_Node.ln_Pri;
  pcb.pcb_Control=PRF_SAVEIO;
  pcb.pcb_Input=*type=='r' ? input : pipe2;
  pcb.pcb_Output=*type=='w' ? output : pipe2;
  pcb.pcb_Console.pcb_SplatFile=errput;
  pcb.pcb_LastGasp=zmsg;

  if (ASyncRun(command, arguments, &pcb)>0L) {
    replyports[fh]=replyport;
    return stream;
  }

  PutMsg(port, (struct Message *)zmsg);
  WaitPort(replyport);
  GetMsg(replyport);

popenerror:
  if (replyport)
    DeletePort(replyport);
  if (zmsg)
    free(zmsg);
  if (ci)
    free(ci);
  if (stream)
    fclose(stream);
  else
    if (pipe1) {
      Close(pipe1);
      _devtab[fh].fd=NULL;
      _devtab[fh].mode=0;
    }
  if (pipe2 && !port)
    closepipe(pipe2);
  return NULL;
}


int pclose(stream)
FILE *stream;
{
  int fh, returncode;
  struct ZombieMsg *zmsg;
  struct MsgPort *replyport;
  struct closeinfo *ci;


  fh=fileno(stream);
  if (!(replyport=replyports[fh]))
    return -1;

  fclose(stream);

  WaitPort(replyport);
  zmsg=(struct ZombieMsg *)GetMsg(replyport);
  returncode=zmsg->zm_ReturnCode;
  ci=(struct closeinfo *)zmsg->zm_UserInfo;

  free(ci);
  free(zmsg);
  DeletePort(replyport);
  replyports[fh]=NULL;
  return returncode;
}

ecphssrw@solaria.csun.edu (Stephen Walton) (01/19/89)

In article <3768@druwy.ATT.COM> mab@druwy.ATT.COM (Alan Bland) writes:
>
>Does anyone have a UNIX-like implementation of popen and pclose that
>uses the 1.3 pipe handler?  I'm porting some UNIX code and would like
>to take advantage of pipes.  I could roll my own, but I haven't yet
>learned all the secrets of file handles with Execute()...

Pipes on the Amiga are really much simpler than you think (I think :-) ).
Just pretend they are files.  Then, instead of something like:
	pipe_handle = popen("command", "w");
you would do
	pipe_handle = fopen("pipe:myfile", "w");
	Execute("command <pipe:myfile", 0L, 0L);
and voila.  You probably want to use mktemp() to generate your pipe
file name, just to be safe.
-- 
Stephen Walton, Dept. of Physics & Astronomy, Cal State Univ. Northridge
RCKG01M@CALSTATE.BITNET       ecphssrw@afws.csun.edu
swalton@solar.stanford.edu    ...!csun!afws.csun.edu!ecphssrw

vkr@osupyr.mast.ohio-state.edu (Vidhyanath K. Rao) (01/22/89)

In article <442@solaria.csun.edu> ecphssrw@solaria.csun.edu
 (Stephen R. Walton) writes:
>Pipes on the Amiga are really much simpler than you think (I think :-) ).
>Just pretend they are files.  Then, instead of something like:
>	pipe_handle = popen("command", "w");
>you would do
>	pipe_handle = fopen("pipe:myfile", "w");
>	Execute("command <pipe:myfile", 0L, 0L);
>and voila.  You probably want to use mktemp() to generate your pipe
>file name, just to be safe.

There is one feature that you should be aware of (I don't know if this also
occurs in *N*X pipes):

    gets() is implemented usually as `Read(Stdin(), sptr, 256L)'
    CON: and, I suspect the ConMan pip: will return when a `newline'
    is encountered. PIPE: *will not*. This irrelevent for stored files.
    But if you use the 1.3 PIPE:, and expect the spawned process to get
    its input as you shove it down the line, you have to pad it at the end.
    -- 
It is the man not the method that                 Nath
solves the problem.                     vkr@osupyr.mast.ohio-state.edu
-Poincare.                                    (614)-366-9341

vkr@osupyr.mast.ohio-state.edu (Vidhyanath K. Rao) (01/22/89)

In article <1200@osupyr.mast.ohio-state.edu> I wrote
>    gets() is implemented usually as `Read(Stdin(), sptr, 256L)'

It should say:
   gets() is usually implemented as
	 `Read(Stdin(), bfrptr, 256L); {other stuff}
-- 
It is the man not the method that                 Nath
solves the problem.                     vkr@osupyr.mast.ohio-state.edu
-Poincare.                                    (614)-366-9341

peter@sugar.uu.net (Peter da Silva) (01/22/89)

In article <1200@osupyr.mast.ohio-state.edu>, vkr@osupyr.mast.ohio-state.edu (Vidhyanath K. Rao) writes:
>     gets() is implemented usually as `Read(Stdin(), sptr, 256L)'

I certainly hope this isn't true. Any implementation of the standard I/O
library that does this is badly broken. Gets should be freely intermixable
with getchar(), fread(), fgets(), and so on... it should behave as if it's:

	gets(s)
	char *s;
	{
		int c;
		char *ptr;

		while((c = getchar()) != EOF) {
			if(c=='\n') break;
			*ptr++ = c;
		}
		*ptr == 0;
		return (c==EOF)?0:s;
	}
-- 
Peter "Have you hugged your wolf today" da Silva  `-_-'  Hackercorp.
...texbell!sugar!peter, or peter@sugar.uu.net      'U`