/* ** Pgmpipe.c --- main module of APipe-Handler ** ** Copyright (C) 1991 by Per Bojsen. All Rights Reserved. ** ** Permission is granted to any individual or institution to use, copy, ** modify, and distribute this software, provided that this complete ** copyright and permission notice is maintained, intact, in all copies ** and supporting documentation. ** ** This software is provided on an "as is" basis without express or ** implied warranty. ** ** NOTE: The code is reentrant. Be careful when modifying it. ** ** $Id: pgmpipe.c,v 1.3 91/10/01 02:35:30 bojsen Exp Locker: bojsen $ ** ** $Log: pgmpipe.c,v $ ** Revision 1.3 91/10/01 02:35:30 bojsen ** Added code to clone current dir of requester process. Changed code ** to obtain pointer to requester process. ** ** Revision 1.2 91/09/24 23:59:44 bojsen ** Fixed typo in #include line. ** ** Revision 1.1 91/09/24 23:54:06 bojsen ** Initial revision ** */ #include #include #include #include #include #include #include #include #ifdef __SASC #include #include #endif /* __SASC */ #include #include "APipe-Handler_rev.h" /* * Version tagging. */ STATIC char VersionTag[] = VERSTAG; /* * Debugging. */ #ifdef DEBUG #define DPrintF(args) do { KPrintF args; } while (0) #else /* !DEBUG */ #define DPrintF(args) #endif /* !DEBUG */ #define AbsExecBase 4L #define LIB_VERSION_SUPPORTED 37L #define PIPEBUFFER_SIZE 4096 /* defines for read/write status */ #define PPIPE_NOTOPEN 0 #define PPIPE_OPEN 4 #define PPIPE_READ 1 #define PPIPE_READOPEN (PGMPIPE_READ | PGMPIPE_OPEN) #define PPIPE_WRITE 2 #define PPIPE_WRITEOPEN (PGMPIPE_WRITE | PGMPIPE_OPEN) #define PPIPE_RWMASK 3 /* defines for fh_Arg1 field of filehandles */ #define PPIPE_READER 1 #define PPIPE_WRITER 2 /* defines for closing variable */ #define PPIPE_C_OPEN 0 #define PPIPE_C_RDRCLOSING 1 #define PPIPE_C_WRTCLOSING 2 #define PPIPE_C_CLOSINGMASK 3 #define PPIPE_C_RDRCLOSED 4 #define PPIPE_C_WRTCLOSED 8 #define PPIPE_C_CLOSEDMASK 0xC /* * Parameter packet to child process. */ struct ChildMsg { struct Message ExecMsg; char *CmdLine; /* the command line to execute */ LONG StackSize; /* use this stacksize for child's child */ BPTR PathList; /* path list for child */ ULONG Flags; /* flags for child */ LONG RC; /* return code from child process */ }; /* Defines for ChildMsg.Flags */ #define CHMF_PIPE 1L /* stdin of child is a pipe (of PIPE: type) */ #define CHMF_PATH 2L /* path is passed in in PathList */ /* * Command path element. */ struct PathEntry { BPTR pe_NextPathEntry; BPTR pe_PathLock; }; /* * The library bases must be global by AmigaOS programming standards, * and the pragma libcall requires them. */ struct ExecBase *SysBase; struct DosLibrary *DOSBase; /* * Prototypes for external functions. */ extern int _ChildProcess(void); /* * Prototypes for local functions. */ ULONG CopyFromFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE *End, ULONG *FIFOFill, UBYTE *Dest, ULONG NumBytes); ULONG CopyToFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE **End, ULONG *FIFOFill, UBYTE *Src, ULONG NumBytes); LONG ExecCommand(char *CmdLine, struct MsgPort **ParentPort, struct Process *Requester, struct FileHandle *ChildIO, LONG IoDirection); BPTR CloneProcessIO(struct Process *Friend, LONG IoDirection); BPTR ClonePathList(struct CommandLineInterface *Peer); void FreePathList(BPTR); void PgmPipe() { struct DosLibrary *l_DOSBase; SysBase = *((struct ExecBase **) AbsExecBase); /* `open' exec.library */ if (l_DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", LIB_VERSION_SUPPORTED)) { struct Process *ourProc, *theirProc; struct DosPacket *packet, *writePkt = NULL, *readPkt = NULL; struct DosPacket *writeCls = NULL, *readCls = NULL; struct MsgPort *childPort = NULL; struct MsgPort *oldConTask = NULL; struct ChildMsg *childMsg = NULL; struct FileHandle *fhIncoming, *fhOutgoing; UBYTE *pipeBuffer; UBYTE *pbStart, *pbEnd; ULONG bytesReady, bytesR = 0, bytesW = 0; UWORD closing = PPIPE_C_RDRCLOSED | PPIPE_C_WRTCLOSED; UWORD readwrite = PPIPE_NOTOPEN; ULONG waitSigMask; struct MinList readQ, writeQ; #ifdef DEBUG ULONG packetNum = 0; #endif /* DEBUG */ NewList((struct List *) &readQ); NewList((struct List *) &writeQ); DOSBase = l_DOSBase; /* patch the global library base */ ourProc = (struct Process *) FindTask(NULL); waitSigMask = 1L << ourProc->pr_MsgPort.mp_SigBit; packet = WaitPkt(); /* get parameter packet */ if ((pipeBuffer = AllocMem(PIPEBUFFER_SIZE, 0)) == NULL) ReplyPkt(packet, DOSFALSE, ERROR_NO_FREE_STORE); else { pbStart = pbEnd = pipeBuffer; bytesReady = 0; /* * Currently we don't use the parameters provided in the parameter * packet. Since we want a new process for each instance of reference * to our handler we don't patch the DeviceList node. */ ReplyPkt(packet, DOSTRUE, packet->dp_Arg2); do { struct Node *node; char *cmdLine; LONG ioerr; if (readPkt && bytesReady == 0 && writePkt == NULL && closing & PPIPE_C_WRTCLOSED) { ReplyPkt(readPkt, bytesR, 0); readPkt = NULL; } #ifdef DEBUG if (readPkt || writePkt) DPrintF(("Event loop: readPkt == 0x%08lx writePkt == 0x%08lx\n", readPkt, writePkt)); #endif /* DEBUG */ if (packet = WaitPkt()) { DPrintF(("Packet %ld, port 0x%08lx: ", ++packetNum, packet->dp_Port)); switch (packet->dp_Type) { struct TagItem adoTags[2]; case ACTION_FINDINPUT: DPrintF(("ACTION_FINDINPUT 0x%08lx 0x%08lx %s\n", packet->dp_Arg1 << 2, packet->dp_Arg2 << 2, (char *) (packet->dp_Arg3 << 2) + 1)); if (readwrite != PPIPE_NOTOPEN) { ReplyPkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE); break; } else readwrite = PPIPE_READ; case ACTION_FINDOUTPUT: #ifdef DEBUG if (packet->dp_Type == ACTION_FINDOUTPUT) DPrintF(("ACTION_FINDOUTPUT 0x%08lx 0x%08lx %s\n", packet->dp_Arg1 << 2, packet->dp_Arg2 << 2, (char *) (packet->dp_Arg3 << 2) + 1)); #endif /* DEBUG */ if (readwrite & PPIPE_OPEN) { ReplyPkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE); break; } else if (readwrite != PPIPE_READ) readwrite = PPIPE_WRITE; fhIncoming = (struct FileHandle *) BADDR(packet->dp_Arg1); /* * Obtain command line to execute. */ cmdLine = (char *) BADDR(packet->dp_Arg3) + 1; while (*cmdLine && *cmdLine != ':') cmdLine++; if (*cmdLine == ':') cmdLine++; /* * Find out who sent the packet. */ if ((packet->dp_Port->mp_Flags & PF_ACTION) == PA_SIGNAL) theirProc = (struct Process *) packet->dp_Port->mp_SigTask; else { ReplyPkt(packet, DOSFALSE, ERROR_BAD_STREAM_NAME); readwrite = PPIPE_NOTOPEN; break; } adoTags[0].ti_Tag = ADO_FH_Mode; adoTags[0].ti_Data = readwrite == PPIPE_READ ? MODE_NEWFILE : MODE_OLDFILE; adoTags[1].ti_Tag = TAG_DONE; if ((fhOutgoing = AllocDosObject(DOS_FILEHANDLE, adoTags)) == NULL) { ReplyPkt(packet, DOSFALSE, ERROR_NO_FREE_STORE); readwrite = PPIPE_NOTOPEN; break; } fhOutgoing->fh_Link = NULL; fhOutgoing->fh_Port = fhIncoming->fh_Port = (struct MsgPort *) 0; fhOutgoing->fh_Type = &ourProc->pr_MsgPort; oldConTask = SetConsoleTask(theirProc->pr_ConsoleTask); DPrintF(("Getting ready to spawn child . . . ")); if (ioerr = ExecCommand(cmdLine, &childPort, theirProc, fhOutgoing, readwrite)) { SetConsoleTask(oldConTask); ReplyPkt(packet, DOSFALSE, ioerr); FreeDosObject(DOS_FILEHANDLE, fhOutgoing); readwrite = PPIPE_NOTOPEN; break; } SetConsoleTask(oldConTask); DPrintF(("Child spawned.\n")); waitSigMask |= 1L << childPort->mp_SigBit; if (readwrite == PPIPE_READ) { fhIncoming->fh_Arg1 = PPIPE_READER; fhOutgoing->fh_Arg1 = PPIPE_WRITER; } else { fhIncoming->fh_Arg1 = PPIPE_WRITER; fhOutgoing->fh_Arg1 = PPIPE_READER; } closing = PPIPE_C_OPEN; readwrite |= PPIPE_OPEN; ReplyPkt(packet, DOSTRUE, packet->dp_Res2); break; case ACTION_READ: DPrintF(("ACTION_READ %ld 0x%08lx %ld\n", packet->dp_Arg1, packet->dp_Arg2, packet->dp_Arg3)); if (packet->dp_Arg2 == NULL || packet->dp_Arg3 < 0) ReplyPkt(packet, DOSFALSE, ERROR_BAD_NUMBER); else if (closing & PPIPE_C_RDRCLOSED) ReplyPkt(packet, DOSFALSE, ERROR_INVALID_LOCK); else if (packet->dp_Arg1 == PPIPE_WRITER) ReplyPkt(packet, 0, 0); else if (readPkt == NULL) { readPkt = packet; bytesR = 0; } else AddTail((struct List *) &readQ, (struct Node *) packet->dp_Link); break; case ACTION_WRITE: DPrintF(("ACTION_WRITE %ld 0x%08lx %ld\n", packet->dp_Arg1, packet->dp_Arg2, packet->dp_Arg3)); if (packet->dp_Arg2 == NULL || packet->dp_Arg3 < 0) ReplyPkt(packet, DOSFALSE, ERROR_BAD_NUMBER); else if (closing & PPIPE_C_WRTCLOSED) ReplyPkt(packet, DOSFALSE, ERROR_INVALID_LOCK); else if (packet->dp_Arg1 == PPIPE_READER) ReplyPkt(packet, 0, 0); else if (writePkt == NULL) { writePkt = packet; bytesW = 0; } else AddTail((struct List *) &writeQ, (struct Node *) packet->dp_Link); break; case ACTION_END: DPrintF(("ACTION_END %ld\n", packet->dp_Arg1)); if (packet->dp_Arg1 == PPIPE_READER && (closing & PPIPE_C_RDRCLOSED) == 0) { closing |= PPIPE_C_RDRCLOSING; readCls = packet; } else if (packet->dp_Arg1 == PPIPE_WRITER && (closing & PPIPE_C_WRTCLOSED) == 0) { closing |= PPIPE_C_WRTCLOSING; writeCls = packet; } else ReplyPkt(packet, DOSTRUE, 0); break; case ACTION_IS_FILESYSTEM: DPrintF(("ACTION_IS_FILESYSTEM\n")); ReplyPkt(packet, DOSFALSE, 0); break; default: DPrintF(("%ld\n", packet->dp_Type)); ReplyPkt(packet, DOSFALSE, ERROR_ACTION_NOT_KNOWN); } DPrintF(("\n")); } do { if (readPkt) { if (bytesReady > 0) bytesR += CopyFromFIFO(pipeBuffer, PIPEBUFFER_SIZE, &pbStart, pbEnd, &bytesReady, (UBYTE *) readPkt->dp_Arg2 + bytesR, readPkt->dp_Arg3 - bytesR); if (bytesR < readPkt->dp_Arg3 && writePkt) { ULONG bytes = MIN(writePkt->dp_Arg3 - bytesW, readPkt->dp_Arg3 - bytesR); CopyMem((UBYTE *) writePkt->dp_Arg2 + bytesW, (UBYTE *) readPkt->dp_Arg2 + bytesR, bytes); bytesR += bytes; if ((bytesW += bytes) == writePkt->dp_Arg3) { ReplyPkt(writePkt, writePkt->dp_Arg3, 0); writePkt = NULL; } } if (bytesR == readPkt->dp_Arg3) { ReplyPkt(readPkt, readPkt->dp_Arg3, 0); readPkt = NULL; } } if (readPkt == NULL && (readPkt = (struct DosPacket *) (node = RemHead((struct List *) &readQ), node ? node->ln_Name : NULL))) { bytesR = 0; } if (writePkt == NULL && (writePkt = (struct DosPacket *) (node = RemHead((struct List *) &writeQ), node ? node->ln_Name : NULL))) { bytesW = 0; } } while (readPkt && (writePkt || bytesReady > 0)); if (readPkt == NULL && closing & PPIPE_C_RDRCLOSING) { closing &= ~PPIPE_C_RDRCLOSING; closing |= PPIPE_C_RDRCLOSED; } while (writePkt && (closing & PPIPE_C_RDRCLOSED || PIPEBUFFER_SIZE - bytesReady >= writePkt->dp_Arg3 - bytesW)) { if (closing & PPIPE_C_RDRCLOSED) { ReplyPkt(writePkt, writePkt->dp_Arg3, 0); writePkt = NULL; } else { ULONG bytes = CopyToFIFO(pipeBuffer, PIPEBUFFER_SIZE, &pbStart, &pbEnd, &bytesReady, (UBYTE *) writePkt->dp_Arg2 + bytesW, writePkt->dp_Arg3 - bytesW); if ((bytesW += bytes) == writePkt->dp_Arg3) { ReplyPkt(writePkt, writePkt->dp_Arg3, 0); writePkt = NULL; } } if (writePkt == NULL && (writePkt = (struct DosPacket *) (node = RemHead((struct List *) &writeQ), node ? node->ln_Name : NULL))) { bytesW = 0; } } if (writePkt == NULL && closing & PPIPE_C_WRTCLOSING) { closing &= ~PPIPE_C_WRTCLOSING; closing |= PPIPE_C_WRTCLOSED; } } while ((closing & PPIPE_C_RDRCLOSED) == 0 || (closing & PPIPE_C_WRTCLOSED) == 0); /* clean up from child process */ if (childPort) { waitSigMask = 1L << childPort->mp_SigBit; while ((childMsg = (struct ChildMsg *) GetMsg(childPort)) == NULL) Wait(waitSigMask); DeleteMsgPort(childPort); } if (childMsg) /* means the child process existed */ { if (readCls) ReplyPkt(readCls, DOSTRUE, 0); if (writeCls) ReplyPkt(writeCls, DOSTRUE, 0); FreePathList(childMsg->PathList); FreeVec(childMsg); /* no use for return code currently */ } FreeMem(pipeBuffer, PIPEBUFFER_SIZE); } CloseLibrary((struct Library *) l_DOSBase); DPrintF(("Handler exiting.\n")); } } /* PgmPipe */ STATIC ULONG CopyFromFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE *End, ULONG *FIFOFill, UBYTE *Dest, ULONG NumBytes) { ULONG bytesCopied = 0, bytes; DPrintF(("CopyFromFIFO: 0x%08lx %ld", Dest, NumBytes)); if (*FIFOFill > 0 && NumBytes > 0) { bytes = *Start >= End ? FIFO + FIFOSize - *Start : *FIFOFill; CopyMem(*Start, Dest, bytesCopied = MIN(bytes, NumBytes)); NumBytes -= bytesCopied; *Start += bytesCopied; *FIFOFill -= bytesCopied; } if (*FIFOFill > 0 && NumBytes > 0) { bytes = End - FIFO; /* invariant: *Start == FIFO + FIFOSize */ CopyMem(FIFO, Dest + bytesCopied, bytes = MIN(bytes, NumBytes)); *Start = FIFO + bytes; *FIFOFill -= bytes; bytesCopied += bytes; } DPrintF((" %ld\n\n", bytesCopied)); return bytesCopied; } /* CopyFromFIFO */ STATIC ULONG CopyToFIFO(UBYTE *FIFO, ULONG FIFOSize, UBYTE **Start, UBYTE **End, ULONG *FIFOFill, UBYTE *Src, ULONG NumBytes) { ULONG bytesCopied = 0, bytes; DPrintF(("CopyToFIFO: 0x%08lx %ld", Src, NumBytes)); if (*FIFOFill == 0) *Start = *End = FIFO; /* normalize for efficiency */ if (*FIFOFill < FIFOSize && NumBytes > 0) { bytes = *End <= *Start ? FIFOSize - *FIFOFill : FIFO + FIFOSize - *End; CopyMem(Src, *End, bytesCopied = MIN(bytes, NumBytes)); NumBytes -= bytesCopied; *End += bytesCopied; *FIFOFill += bytesCopied; } if (*FIFOFill < FIFOSize && NumBytes > 0) { bytes = *Start - FIFO; /* invariant: *End == FIFO + FIFOSize */ CopyMem(Src + bytesCopied, FIFO, bytes = MIN(bytes, NumBytes)); *End = FIFO + bytes; *FIFOFill += bytes; bytesCopied += bytes; } DPrintF((" %ld\n\n", bytesCopied)); return bytesCopied; } /* CopyFromFIFO */ #define FAILATCMD "FailAt 2147483647\n" #define STRLEN_FAILATCMD 18 /* * Run a command asynchronously. */ STATIC LONG ExecCommand(char *CmdLine, struct MsgPort **ParentPort, struct Process *Requester, struct FileHandle *ChildIO, LONG IoDirection) { char *commandLine; struct ChildMsg *childMsg; struct TagItem processTags[9]; struct Process *child, *thisProcess = (struct Process *) FindTask(NULL); struct CommandLineInterface *cli = BADDR(Requester->pr_CLI); BPTR childOI; struct LocalVar *pVar; BPTR pathList, homeDir; LONG ioErr; /* * Copy shell variables, aliases, and path from requester process. * NOTE: This is not completely safe! */ Forbid(); for (pVar = (struct LocalVar *) Requester->pr_LocalVars.mlh_TailPred; pVar->lv_Node.ln_Pred; pVar = (struct LocalVar *) pVar->lv_Node.ln_Pred) { if (FindVar(pVar->lv_Node.ln_Name, pVar->lv_Node.ln_Type) == NULL) SetVar(pVar->lv_Node.ln_Name, pVar->lv_Value, pVar->lv_Len, pVar->lv_Flags | pVar->lv_Node.ln_Type & ~LVF_IGNORE); } pathList = ClonePathList(cli); Permit(); if ((childOI = CloneProcessIO(Requester, IoDirection)) == NULL) { ioErr = IoErr(); FreePathList(pathList); return ioErr; } if ((homeDir = DupLock(Requester->pr_CurrentDir)) == NULL) { ioErr = IoErr(); Close(childOI); FreePathList(pathList); return ioErr; } /* * Create parent's port. */ if ((*ParentPort = CreateMsgPort()) == NULL) { UnLock(homeDir); Close(childOI); FreePathList(pathList); return ERROR_NO_FREE_STORE; } /* * Build command line from argument vector. First count the length. * * We insert a `FailAt 2147483647' (0x7fffffff) so the shell doesn't * print `Command foo failed' when we call System() to execute the * command. */ if ((childMsg = AllocVec(sizeof(struct ChildMsg) + strlen(CmdLine) + STRLEN_FAILATCMD + 1, MEMF_PUBLIC)) == NULL) { DeleteMsgPort(*ParentPort); *ParentPort = NULL; UnLock(homeDir); Close(childOI); FreePathList(pathList); return ERROR_NO_FREE_STORE; } childMsg->ExecMsg.mn_Node.ln_Type = NT_MESSAGE; childMsg->ExecMsg.mn_Node.ln_Pri = 0; childMsg->ExecMsg.mn_ReplyPort = *ParentPort; childMsg->ExecMsg.mn_Length = sizeof(struct ChildMsg); childMsg->CmdLine = commandLine = (char *) (childMsg + 1); /* Setup the grandchild's [sic] stack */ childMsg->StackSize = cli ? cli->cli_DefaultStack << 2 : Requester->pr_StackSize; childMsg->PathList = pathList; childMsg->Flags = pathList ? CHMF_PATH : 0; childMsg->RC = 0; strcpy(commandLine, FAILATCMD); strcpy(commandLine + STRLEN_FAILATCMD, CmdLine); processTags[0].ti_Tag = NP_Entry; processTags[0].ti_Data = (Tag) _ChildProcess; processTags[1].ti_Tag = NP_Input; processTags[2].ti_Tag = NP_Output; processTags[3].ti_Tag = NP_StackSize; processTags[3].ti_Data = thisProcess->pr_StackSize; processTags[4].ti_Tag = NP_Cli; processTags[4].ti_Data = TRUE; processTags[5].ti_Tag = NP_Name; processTags[5].ti_Data = (Tag) "Kicker Process"; processTags[6].ti_Tag = NP_Priority; processTags[6].ti_Data = Requester->pr_Task.tc_Node.ln_Pri; processTags[7].ti_Tag = NP_CurrentDir; processTags[7].ti_Data = homeDir; processTags[8].ti_Tag = TAG_DONE; if (IoDirection == PPIPE_READ) { processTags[1].ti_Data = childOI; processTags[2].ti_Data = MKBADDR(ChildIO); } else /* IoDirection == PPIPE_WRITE */ { processTags[1].ti_Data = MKBADDR(ChildIO); processTags[2].ti_Data = childOI; } /* * Start our `kicker' process. This process consists of the _ChildProcess() * function above. The _ChildProcess() function then executes the command. */ if ((child = CreateNewProc(processTags)) == NULL) { FreeVec(childMsg); DeleteMsgPort(*ParentPort); *ParentPort = NULL; UnLock(homeDir); Close(childOI); FreePathList(pathList); return ERROR_NO_FREE_STORE; } /* now pass the child the startup message */ PutMsg(&child->pr_MsgPort, (struct Message *) childMsg); return 0; } /* ExecCommand */ STATIC BPTR CloneProcessIO(struct Process *Friend, LONG IoDirection) { BPTR origFH; BPTR lock; BPTR fh; if (IoDirection == PPIPE_READ) origFH = Friend->pr_CIS; else origFH = Friend->pr_COS; if ((lock = DupLockFromFH(origFH)) == NULL || (fh = OpenFromLock(lock)) == NULL) { if (lock) UnLock(lock); fh = Open("CONSOLE:", MODE_OLDFILE); } return fh; } /* CloneProcessIO */ /* * NOTE: This routine should be called from within a Forbid(); * but it is still unsafe due to the call to DupLock(). */ STATIC BPTR ClonePathList(struct CommandLineInterface *Peer) { BPTR pathList = NULL; struct PathEntry *pathEntry, *pathET; if (Peer) { for (pathEntry = BADDR(Peer->cli_CommandDir), pathET = (struct PathEntry *) &pathList; pathEntry; pathEntry = BADDR(pathEntry->pe_NextPathEntry)) { struct PathEntry *newPE; if (newPE = AllocMem(sizeof(struct PathEntry), MEMF_PUBLIC)) { newPE->pe_NextPathEntry = NULL; newPE->pe_PathLock = DupLock(pathEntry->pe_PathLock); pathET->pe_NextPathEntry = MKBADDR(newPE); pathET = newPE; } else break; } } return pathList; } /* ClonePathList */ STATIC void FreePathList(BPTR PathList) { struct PathEntry *pathEntry; while (pathEntry = BADDR(PathList)) { PathList = pathEntry->pe_NextPathEntry; UnLock(pathEntry->pe_PathLock); FreeMem(pathEntry, sizeof(struct PathEntry)); } } /* FreePathList */