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