amiga-libnix3/extra/pipeoc.c

367 lines
11 KiB
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.
]*/
/*
* $Id: amiga.c,v 1.1.1.2 1998/04/15 19:21:59 lhecking Exp $
*
* 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>
/* 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
#include <errno.h>
/* 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;
}