/* exec_cmd.c a set of procedures to support executing commands (including the communication between two processes using pipes); used mainly to support UNIX filters A. Stochniol, Last revision 1.09.1994 */ /* * Copyright 1991 - 1994, Andrzej Stochniol, London, UK * * ASEDIT text editor, both binary and source (hereafter, Software) is * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains * with AS. * * AS grants you (hereafter, Licensee) a license to use the Software * for academic, research and internal business purposes only, without a * fee. Licensee may distribute the binary and source code (if released) * to third parties provided that the copyright notice and this statement * appears on all copies and that no charge is associated with such copies. * * Licensee may make derivative works. However, if Licensee distributes * any derivative work based on or derived from the Software, then * Licensee will: * (1) notify AS regarding its distribution of the derivative work, and * (2) clearly notify users that such derivative work is a modified version * and not the original ASEDIT distributed by AS. * * Any Licensee wishing to make commercial use of the Software should * contact AS to negotiate an appropriate license for such commercial use. * Commercial use includes: * (1) integration of all or part of the source code into a product for sale * or license by or on behalf of Licensee to third parties, or * (2) distribution of the binary code or source code to third parties that * need it to utilize a commercial product sold or licensed by or on * behalf of Licensee. * * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS * SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR * IMPLIED WARRANTY. IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * By using or copying this Software, Licensee agrees to abide by the * copyright law and all other applicable laws, and the terms of this * license. * AS shall have the right to terminate this license immediately by * written notice upon Licensee's breach of, or non-compliance with, any * of its terms. Licensee may be held legally responsible for any * copyright infringement that is caused or encouraged by Licensee's * failure to abide by the terms of this license. * * * Andrzej Stochniol (A.Stochniol@ic.ac.uk) * 30 Hatch Road * London SW16 4PN * UK */ #include #include #include #include #include #include #include #include #include #include /* to define toupper function */ #include #include #include #ifdef SYSV # ifndef __hpux # define vfork() fork() # endif /* __hpux */ #endif /* SYSV */ #include /* needed because of XmAddWMProtocolCallback; available since X11R4 */ #include "asedit.h" extern Widget toplevel; extern XmStringCharSet charset; #define PARTIAL_BUFSIZ 128 typedef struct partialBufStruct { struct partialBufStruct *prev; /* pointer to the previous partial buffer */ int len; char txt[PARTIAL_BUFSIZ]; } partialBufStruct; typedef struct { int fd; /* -1 for closed pipe */ Boolean collect; char *txt; long len; long pos; partialBufStruct *pbufLast; /* last of chained partial buffers (follow the chain using pbufLast->prev)*/ void *ccs; /* back-pointer to the childCommStruct (needs casting) */ } commPipeStruct; /* childOutDepot might be (defined as enum in asedit.h): TO_NONE TO_STDOUT TO_STDERR TO_CURRENT -> with that the left and right make sense only TO_NEW TO_TEXT_DIALOG */ typedef struct { pid_t childpid; /* child process id */ aseditWindowStruct *win; /* pointer to the parent edit structure */ int childOutDepot; /* where we want the child's stdout to be shown */ char *dlgTitle; /* dialog title (valid only for TO_*_DIALOG case) */ char *dialogsTitle; /* title for all other dialogs (work, message etc) */ int noOutDialog; /* type of dialog when no output was obtained */ XmString noOutMsg; /* message when no output was obtained (and noOutDialog declared) */ XmTextPosition left; /* left position of the text to be replaced with the child's output */ XmTextPosition right; /* right ... (respectively); make sense only for the TO_CURRENT case */ commPipeStruct in; /* input pipe structure */ commPipeStruct out; /* output pipe structure (output will be deposited according to childOutDepot) */ commPipeStruct err; /* error pipe structure (error pipe output is always shown in a question dialog */ XtIntervalId intervalId; /* interval Id for the timeout procedure */ Widget progress; /* widget to show progress of the command */ Boolean abortedByUser; /* a flag set by destroy CB */ } childCommStruct; #ifdef _NO_PROTO static void closePipe(p) int *p; #else /* ! _NO_PROTO */ static void closePipe(int *p) #endif { if(p[0] >= 0) close(p[0]); if(p[1] >= 0) close(p[1]); } #ifdef _NO_PROTO static void closePipes( inPipe, outPipe, errPipe) int *inPipe; int *outPipe; int *errPipe; #else /* ! _NO_PROTO */ static void closePipes( int *inPipe, int *outPipe, int *errPipe) #endif { closePipe(inPipe); closePipe(outPipe); closePipe(errPipe); } #ifdef _NO_PROTO static void acceptOrCancelCB (w, response, cbs) Widget w; int *response; XmAnyCallbackStruct *cbs; #else /* ! _NO_PROTO */ static void acceptOrCancelCB (Widget w, int *response, XmAnyCallbackStruct *cbs) #endif { if (cbs->reason == XmCR_OK) *response = 1; else if (cbs->reason == XmCR_CANCEL) *response = 0; else /* called from the WM_DELETE_WINDOW protocol message sent by the wm */ *response = 0; /* assign cancel situation */ } /* acceptOrCancelCB */ /* acceptChildOutput: stand-alone procedure to create modal dialog and ask the user question about proceeding by showing the error string Note that the instance name depends on the defaultButtonType */ #ifdef _NO_PROTO Boolean acceptChildOutput(parent, errstr, defaultButtonType) Widget parent; char *errstr; int defaultButtonType; #else /* ! _NO_PROTO */ Boolean acceptChildOutput(Widget parent, char *errstr, int defaultButtonType) #endif { Widget dlg, shell; Arg al[5]; /* arg list */ register int ac = 0; /* arg count */ XmString xmstr; /* work XmString */ int response = -1; XtAppContext app = XtWidgetToApplicationContext(parent); Atom WM_DELETE_WINDOW; if(errstr) /* otherwise take the string from resources */ { xmstr = XmStringCreateLtoR (errstr, charset); XtSetArg(al[ac], XmNmessageString, xmstr); ac++; } /* do NOT do anything when the user presses the Close button in the system menu; what kind of answer would be that; */ XtSetArg (al[ac], XmNdeleteResponse, XmDO_NOTHING); ac++; /****XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); ac++; ***/ XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ac++; XtSetArg(al[ac], XmNdefaultButtonType, defaultButtonType); ac++; if(defaultButtonType == XmDIALOG_CANCEL_BUTTON) dlg = XmCreateQuestionDialog (parent, "errorChild", al, ac); else dlg = XmCreateQuestionDialog (parent, "messageChild", al, ac); XtAddCallback (dlg, XmNokCallback, (XtCallbackProc)acceptOrCancelCB, (XtPointer)&response); XtAddCallback (dlg, XmNcancelCallback, (XtCallbackProc)acceptOrCancelCB, (XtPointer)&response); XtUnmanageChild (XmMessageBoxGetChild (dlg, XmDIALOG_HELP_BUTTON)); XtManageChild (dlg); /* take care for the situation when the window manager sends the WM_DELETE_WINDOW protocol message */ shell = XtParent (dlg); WM_DELETE_WINDOW = XmInternAtom (XtDisplay (parent), "WM_DELETE_WINDOW", False); XmAddWMProtocolCallback (shell, WM_DELETE_WINDOW, (XtCallbackProc)acceptOrCancelCB, (XtPointer)&response); /* loop until we get the response */ while (response == -1) { XtAppProcessEvent (app, XtIMAll); XSync (XtDisplay (dlg), 0); } XtUnmanageChild (dlg); XSync (XtDisplay (dlg), 0); XmUpdateDisplay (dlg); XtDestroyWidget (dlg); if(response > 0) return True; else return False; } /* acceptChildOutput */ #ifdef _NO_PROTO static void freePartialBuffers(ps) commPipeStruct *ps; #else /* ! _NO_PROTO */ static void freePartialBuffers(commPipeStruct *ps) #endif { partialBufStruct *pbuf, *pbuf_prev; /* we don't need partial buffers any more; free the memory (if it wasn't freed yet) */ pbuf=ps->pbufLast; while(pbuf != NULL) { pbuf_prev = pbuf->prev; XtFree((char *)pbuf); pbuf = pbuf_prev; } ps->pbufLast = (partialBufStruct *) NULL; } /* freePartialBuffers */ #ifdef _NO_PROTO static void dialogCloseCB(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; #else /* ! _NO_PROTO */ static void dialogCloseCB(Widget dialog, XtPointer client_data, XtPointer call_data) #endif { /* called when the user presses the cancel button; we simply destroy the widget (that would cause the call to dialogDestroyCB); when the user dsmisses the dialog by pressing Close button in the window menu the progressDestroyCB is called directly; */ XtDestroyWidget(dialog); } #ifdef _NO_PROTO static void dialogDestroyCB(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; #else /* ! _NO_PROTO */ static void dialogDestroyCB(Widget dialog, XtPointer client_data, XtPointer call_data) #endif { /* called when the dialog is destroyed; see above */ ; /* do nothing here */ } #ifdef _NO_PROTO static void showNewTextDialog(win, txt, dlgTitle) aseditWindowStruct *win; char *txt; char *dlgTitle; #else /* ! _NO_PROTO */ static void showNewTextDialog(aseditWindowStruct *win, char *txt, char *dlgTitle) #endif { /* create new, simple text dialog and show the txt in it; make the dialog as small as possible */ Widget kid[5]; /* Children to unmanage */ Arg al[18]; /* Arg List */ register int ac = 0; /* Arg Count */ Dimension rows=1, cols=1; long lines=1L, columns=1L, colsMax=1L; long i, len; int defColsMax=40, defRowsMax=12; /* hardcoded in the free version (half of the standard window)*/ Widget dialog, text; XmString xmstr; /* work XmString */ int tab_size = win->tabsize; XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY); ac++; /* for Close in control menu */ XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++; if(dlgTitle) { xmstr =XmStringCreateLtoR(dlgTitle, charset); XtSetArg (al[ac], XmNdialogTitle, xmstr); ac++; } if(xmUseVersion >= 1002) dialog = XmCreateWarningDialog(win->menu_bar, "text_dialog", al, ac); else /* use workaround for all versions before Motif 1.2 */ dialog = XmCreatePromptDialog(win->menu_bar, "text_dialog", al, ac); if(dlgTitle) XmStringFree(xmstr); /* free memory allocated for XmString */ if(dialog) /* safety check */ { /* unmanage the unneeded buttons and elements */ i = 0; if(xmUseVersion >= 1002) { kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON); kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_MESSAGE_LABEL); kid[i++] = XmMessageBoxGetChild(dialog, XmDIALOG_SYMBOL_LABEL); } else { kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON); kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL); kid[i++] = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); } XtUnmanageChildren (kid, i); /* find the number of lines and the maximum line width of the text to be displayed (it is used to minimize the size of the dialog; note that the algorithm is correct only when there is no wrapping;) */ if(txt && (len=strlen(txt)) > 0) { for(i=0; i colsMax) colsMax = columns; columns = 1L; if(i == len -1) lines--; /* do not show the last line if it only consist of line feed */ } else { if(txt[i] != '\t') columns++; else columns = ((columns+tab_size-1)/tab_size)*tab_size + 1L; } } /* check the last line */ if(columns > colsMax) colsMax = columns; } if(colsMax > defColsMax) cols = defColsMax; else cols = colsMax; if(lines > defRowsMax) rows = defRowsMax; else rows = lines; /* now add the scrolled text */ ac = 0; XtSetArg (al[ac], XmNrows, rows); ac++; XtSetArg (al[ac], XmNcolumns, cols); ac++; XtSetArg (al[ac], XmNresizeWidth, False); ac++; XtSetArg (al[ac], XmNresizeHeight, False); ac++; XtSetArg (al[ac], XmNscrollVertical, True); ac++; XtSetArg (al[ac], XmNeditable, False); ac++; XtSetArg (al[ac], XmNeditMode, XmMULTI_LINE_EDIT); ac++; XtSetArg (al[ac], XmNbackground, text_read_only_background); ac++; XtSetArg (al[ac], XmNvalue, txt); ac++; /* add the vertical scrolling for long output */ if(lines > defRowsMax) { XtSetArg(al[ac], XmNscrollVertical, True); ac++; } else { XtSetArg(al[ac], XmNscrollVertical, False); ac++; } /* Note that we set wordWrap and scrollHorizontal in the app defaults file */ text = XmCreateScrolledText (dialog, "text", al, ac); XtManageChild(text); XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)dialogCloseCB, NULL); XtAddCallback(dialog, XmNdestroyCallback, (XtCallbackProc)dialogDestroyCB, NULL); XtManageChild(dialog); } } /* showNewTextDialog */ #ifdef _NO_PROTO static void showNoOutDialog(parent, dlgType, title, msg) Widget parent; int dlgType; char *title; XmString msg; #else /* ! _NO_PROTO */ static void showNoOutDialog(Widget parent, int dlgType, char *title, XmString msg) #endif { register int ac; /* arg count */ Arg al[7]; /* arg list */ XmString xmstr; /* work XmString */ Widget dialog; ac = 0; if(title) { xmstr =XmStringCreateLtoR(title, charset); XtSetArg(al[ac], XmNdialogTitle, xmstr); ac++; } if(msg != (XmString) NULL) { XtSetArg(al[ac], XmNmessageString, msg); ac++; } XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY); ac++; /* for Close in control menu */ XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++; switch(dlgType) { case XmDIALOG_ERROR: dialog = XmCreateErrorDialog(parent, "noOutDialog", al, ac); break; case XmDIALOG_INFORMATION: dialog = XmCreateInformationDialog(parent, "noOutDialog", al, ac); break; case XmDIALOG_MESSAGE: dialog = XmCreateMessageDialog(parent, "noOutDialog", al, ac); break; case XmDIALOG_WARNING: dialog = XmCreateWarningDialog(parent, "noOutDialog", al, ac); break; default: fprintf(stderr, "\nUnknown noOutDialog type!"); break; } if(dialog) /* safety check */ { XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_OK_BUTTON)); XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON)); XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)dialogCloseCB, NULL); XtAddCallback(dialog, XmNdestroyCallback, (XtCallbackProc)dialogDestroyCB, NULL); XtManageChild(dialog); } if(title) XtFree((char *)xmstr); /* free memory allocated */ } /* showNoOutDialog */ #ifdef _NO_PROTO static void processChildOutput(ccs) childCommStruct *ccs; #else /* ! _NO_PROTO */ static void processChildOutput(childCommStruct *ccs) #endif { int status; Boolean abnormal_end = False, acceptOutput; aseditWindowStruct *win; Widget dialog; /* check if all pipes are closed; if so now is the time to deal with the child output; if not return and wait for the next time to be called */ if(ccs->out.fd >=0 || ccs->err.fd >= 0 || ccs->in.fd >= 0) return; /* wait for the child to complete (reap the child); check the return status*/ waitpid(ccs->childpid, &status, 0); /* if there is an error output, ask the user about proceeding; note that error might be related to abnormal termination of the child (see status) or just a message sent to stderr (like adding the final line feed); we distinguish between severity of errors by providing different default button for the user to decide what to do */ /* If you get errors around WIFEXITED then read the note below: Note: that on some systems you might have to add "-D_POSIX_SOURCE" definition for the compiler to properly include the ; That was the case for IBM RS/6000 AIX 3.2.3 without xmkmf (but on AIX 3.2.5 all was OK). */ if(WIFEXITED(status)) { abnormal_end = False; /* normal termination; exit status might be obtained using WEXITSTATUS(status) */ if(WEXITSTATUS(status) != 0) abnormal_end = True; /* lets mark this as abnormal as well; (e.g. this is the case when command caled from inside a shell was not found!) */ } else if (WIFSIGNALED(status)) abnormal_end = True; /* the third possiblity is that the child stopped */ /* we also mark the abnormal termination if any of the pipes was closed with an error (for example as a result of kill command) */ if(ccs->out.fd < -1 || ccs->err.fd < -1 || ccs->in.fd < -1) abnormal_end = True; win = ccs->win; if(ccs->abortedByUser) { acceptOutput = False; /* no matter how the child has finished (or was reported to finish) do not process the output */ } else { XFlush(XtDisplay(ccs->win->menu_bar)); if(ccs->progress) { /* get rid off the progress dialog */ XtDestroyWidget(ccs->progress); } else XtRemoveTimeOut(ccs->intervalId); /* remove the timer NOW */ if(abnormal_end) { /* if there was some output on stderr it would be used, if not the string from resources will be used (because ccs->err.txt is NULL in such case) */ acceptOutput = acceptChildOutput(win->menu_bar, ccs->err.txt, XmDIALOG_CANCEL_BUTTON); } else if (ccs->err.len) { acceptOutput = acceptChildOutput(win->menu_bar, ccs->err.txt, XmDIALOG_OK_BUTTON); } else acceptOutput = True; } if(acceptOutput) { /* do what is neccessary ... */ /* first check if the length of the output is zero and if the user specified noOutDialog; if so show this dialog; if not process as usual */ if(ccs->out.len == 0 && (ccs->noOutDialog == XmDIALOG_ERROR || ccs->noOutDialog == XmDIALOG_INFORMATION || ccs->noOutDialog == XmDIALOG_MESSAGE || ccs->noOutDialog == XmDIALOG_WARNING) ) { showNoOutDialog(win->menu_bar, ccs->noOutDialog, ccs->dialogsTitle, ccs->noOutMsg); } else switch(ccs->childOutDepot) { case TO_NONE: ; /* do nothing (discard output) */ break; case TO_STDOUT: if(ccs->out.txt) fprintf(stdout, ccs->out.txt); break; case TO_STDERR: if(ccs->out.txt) fprintf(stderr, ccs->out.txt); break; case TO_CURRENT: /* replace the original selection and be sure that the insertion point is moved just after the inserted text */ disableRedisplay(win); /* to prevent visual flashing */ XmTextReplace(win->edit_text, ccs->left, ccs->right, ccs->out.txt); if(ccs->out.txt) /* not NULL */ { /* set the selection again */ /* note that XmTextSelection position changes the insertion to the right pos. */ XmTextSetSelection(win->edit_text, ccs->left, ccs->left + strlen(ccs->out.txt), CurrentTime); /** XmTextSetInsertionPosition(win->edit_text, ccs->left + strlen(ccs->out.txt)); not needed */ } enableRedisplay(win); break; case TO_NEW: next_window(asedit_last_window); /* create next window */ open_file_in_last_window(NULL); /* open a new default file ... */ ccs->left = ccs->right = (XmTextPosition) 0; XmTextReplace(asedit_last_window->edit_text, ccs->left, ccs->right, ccs->out.txt); if(ccs->out.txt) /* not NULL */ XmTextSetInsertionPosition(asedit_last_window->edit_text, ccs->left + strlen(ccs->out.txt)); break; case TO_TEXT_DIALOG: /* only show it if there was any output; otherwise show standard info */ if(ccs->out.len > 0) showNewTextDialog(win, ccs->out.txt, ccs->dlgTitle); else showNoOutDialog(win->menu_bar, XmDIALOG_INFORMATION, ccs->dialogsTitle, ccs->noOutMsg); break; default: fprintf(stderr,"\nUnknown depository for the child's output!"); fprintf(stderr,"\nUsing parent's stderr! The output follows:"); if(ccs->out.txt) fprintf(stderr, ccs->out.txt); break; } } /* now release all memory */ freePartialBuffers(&ccs->in); freePartialBuffers(&ccs->out); freePartialBuffers(&ccs->err); if(ccs->in.txt) XtFree(ccs->in.txt); if(ccs->out.txt) XtFree(ccs->out.txt); if(ccs->err.txt) XtFree(ccs->err.txt); /* free allocated memory */ if(ccs->dlgTitle) XtFree(ccs->dlgTitle); if(ccs->dialogsTitle) XtFree(ccs->dialogsTitle); if(ccs->progress) /* if we were extremely unlucky get rid of the progress dialog NOW */ XtDestroyWidget(ccs->progress); XtFree((char *)ccs); } /* processChildOutput */ #ifdef _NO_PROTO static void createTextFromPartialBuffers(ps) commPipeStruct *ps; #else /* ! _NO_PROTO */ static void createTextFromPartialBuffers(commPipeStruct *ps) #endif { long l, len = 0L; int i; partialBufStruct *pbuf; /* find the how much data is stored in partial buffers and allocate memory for the whole lot */ for (pbuf=ps->pbufLast; pbuf != NULL; pbuf=pbuf->prev) len += pbuf->len; if(len == 0L) { ps->len = 0; return; } /* nothing stored, return immediately */ ps->txt = XtMalloc(len+1); /* store the length, end the text with a null, then construct the text starting from the end */ ps->len = l = len; ps->txt[l--] = '\0'; for (pbuf=ps->pbufLast; pbuf != NULL; pbuf=pbuf->prev) { for(i = pbuf->len-1; i>=0; i--) ps->txt[l--] = pbuf->txt[i]; } /* set the initial position of the text (it must be now 0) */ ps->pos = l+1; /* we don't need partial buffers any more; free the memory */ freePartialBuffers(ps); } /* createTextFromPartialBuffers */ #ifdef _NO_PROTO static void childOutErrPipeCB(client_data, source, id) XtPointer client_data; int *source; XtInputId *id; #else /* ! _NO_PROTO */ static void childOutErrPipeCB(XtPointer client_data, int *source, XtInputId *id) #endif { commPipeStruct *cps = (commPipeStruct *) client_data; partialBufStruct *pbuf; int i, nbytes; pbuf = (partialBufStruct *) XtMalloc(sizeof(partialBufStruct)); nbytes = read(*source, pbuf->txt, PARTIAL_BUFSIZ); if( nbytes == -1) { /* error */ /* check for the following non-blocking and interrupt i/o */ /* AIX returns EAGAIN where 4.3BSD used EWOULDBLOCK; check for systems that return either EAGAIN or EWOULDBLOCK. */ if (errno == EWOULDBLOCK || errno == EAGAIN) { return; /* do nothing */ } } if (nbytes == 0 || nbytes == -1) { /* nbytes=0 means EOF on pipe */ XtFree((char *)pbuf); XtRemoveInput(*id); close(*source); createTextFromPartialBuffers(cps); /* put the partial buffers together */ cps->fd = nbytes-1; /* source was closed; we use -2 to find that it was closed with an error */ /* check if the other pipes were closed already; if so now is the time to deal with the child output */ processChildOutput(cps->ccs); return; } else { /* process the input, i.e. store it in the partial buffer */ pbuf->len = nbytes; pbuf->prev = cps->pbufLast; cps->pbufLast = pbuf; } } /* childOutErrPipeCB */ #ifdef _NO_PROTO static void childInPipeCB(client_data, source, id) XtPointer client_data; int *source; XtInputId *id; #else /* ! _NO_PROTO */ static void childInPipeCB(XtPointer client_data, int *source, XtInputId *id) #endif { commPipeStruct *cps = (commPipeStruct *) client_data; int i, nbytes; if(cps->pos < cps->len) nbytes = write(*source, cps->txt+cps->pos, cps->len - cps->pos); else nbytes = 0; /* we've already written out everything */ if( nbytes == -1) { /* error */ /* check for the following non-blocking and interrupt i/o */ /* AIX returns EAGAIN where 4.3BSD used EWOULDBLOCK; check for systems that return either EAGAIN or EWOULDBLOCK. */ /******old if (errno != EWOULDBLOCK && errno != EAGAIN) { |* really fatal ! *| |* perror .... *| } ******/ if (errno == EWOULDBLOCK || errno == EAGAIN) { return; /* do nothing */ } } if (nbytes == 0 || nbytes == -1) { /* nbytes=0 means EOF on pipe */ XtRemoveInput(*id); close(*source); cps->fd = nbytes-1; /* source was closed; we use -2 to find that it was closed with an error */ /* check if the other pipes were closed already; if so now is the time to deal with the child output */ processChildOutput(cps->ccs); return; } else { /* change the position in the parent output buffer */ cps->pos += nbytes; } } /* childInPipeCB */ /* forkAnPipe is used to execute a process (command) and establish two way communications using pipes between the parent and the child processes Returns childpid if succesfull. */ #ifdef _NO_PROTO static pid_t forkAndPipe(w, cmd, inPipe, outPipe, errPipe) Widget w; String cmd; int *inPipe; int *outPipe; int *errPipe; #else /* ! _NO_PROTO */ static pid_t forkAndPipe(Widget w, String cmd, int *inPipe, int *outPipe, int *errPipe) #endif { /* all *Pipe's should point to arrays of 2 !!! */ pid_t childpid; #ifdef __hpux int (*istat)(), (*qstat)(); #else void (*istat)(), (*qstat)(); #endif /* set the default values to a value < 0 to recognize if a file descriptor exists */ inPipe[0] = inPipe[1] = -1; errPipe[0] = errPipe[1] = -1; outPipe[0] = outPipe[1] = -1; if (pipe(inPipe) < 0 || pipe(outPipe) < 0 || pipe(errPipe) < 0 ) { closePipes(inPipe, outPipe, errPipe); return -2; /*we might want to treat that as fatal error */ } if ( (childpid = fork()) < 0) /** on some systems use vfork !!! ***/ { closePipes(inPipe, outPipe, errPipe); return -1; /* we might want to treat that as fatal error */ } else if (childpid == 0) { /* first reset the handling of SIGINT, SIGQUIT and SIGHUP */ signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL); #ifdef SIGQUIT /* not all systems define that (HP-UX does not) */ signal(SIGQUIT, SIG_DFL); #endif /* child process */ dup2(inPipe[0], fileno(stdin)); /* stdin */ dup2(outPipe[1], fileno(stdout)); /* stdout */ dup2(errPipe[1], fileno(stderr)); /* stderr */ /* close unused pipe ends */ close(inPipe[1]); /* write end */ close(outPipe[0]); /* read end */ close(errPipe[0]); (void) close(XConnectionNumber(XtDisplay(w))); /**** or simply close(XConnectionNumber(dpy)); ***/ /* set the process group ID and session ID of the calling process to the process ID of the calling process (in other words make it a process group leader); this will allow us to kill this and all its subprocesses with a single kill command (with negative pid value; on BSD you can use killpg, but don't mix POSIX and BSD !) */ setsid(); /* to run a program use: execlp("li", "li", "-al", 0); */ /* to run the shell to interpret the command use: execl("/usr/bin/sh", "sh", "-c", "li -l *.c", 0); */ execl("/bin/sh", "sh", "-c", cmd, 0); _exit(127); } else { /* parent */ /* close unused pipe ends for the parent side */ close(inPipe[0]); close(outPipe[1]); close(errPipe[1]); signal(SIGPIPE, SIG_IGN); /* ignore pipe signals (we will catch the child processed that died or was killed in our *PipeCB's*/ } return childpid; } /* forkAndPipe */ #ifdef _NO_PROTO static void set_nonblock(fd) int fd; #else /* ! _NO_PROTO */ static void set_nonblock(int fd) #endif { int val; if ( (val = fcntl(fd, F_GETFL, 0)) < 0) perror("asedit - set_nonblock: fcntl F_GETFL error"); val |= O_NONBLOCK; if (fcntl(fd, F_SETFL, val) < 0) perror("asedit - set_nonblock: fcntl F_SETFL error"); } #ifdef _NO_PROTO void progressStopCB(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; #else /* ! _NO_PROTO */ void progressStopCB(Widget dialog, XtPointer client_data, XtPointer call_data) #endif { /* called when the user presses the OK button; we simply destroy the widget (that would cause the call to progressDestroyCB); when the user dsmisses the dialog by pressing Close button in the window menu the progressDestroyCB is called directly; */ XtDestroyWidget(dialog); } #ifdef _NO_PROTO void progressDestroyCB(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; #else /* ! _NO_PROTO */ void progressDestroyCB(Widget dialog, XtPointer client_data, XtPointer call_data) #endif { /* called when the dialog is destroyed; see above */ childCommStruct *ccs = (childCommStruct *)client_data; XtRemoveTimeOut(ccs->intervalId); kill(-ccs->childpid, SIGTERM); /* kill the child process group; we will collect the output and release memory in other callbacks */ ccs->abortedByUser = True; /* set the flag so we won't ask the user if proceed with partial results */ ccs->progress = NULL; } /* showCmdProgress - show progress of the command, restarts timer and saves timer id; first time creates the special dialog to show progress Note that if the output of the filter goes to the current window we create the dialog as a PRIMARY_APPLICATION_MODAL, so the user can't spoil anything in the editor. */ #ifdef _NO_PROTO static void showCmdProgress(client_data, id) XtPointer client_data; XtIntervalId *id; #else /* ! _NO_PROTO */ static void showCmdProgress(XtPointer client_data, XtIntervalId *id) #endif { childCommStruct *ccs = (childCommStruct *)client_data; XtAppContext app_context = XtWidgetToApplicationContext(ccs->win->menu_bar); if(ccs->progress == NULL) { /* first create the progress dialog and register a callback to stop the command */ Arg al[5]; /* arg list */ register int ac = 0; /* arg count */ XtSetArg(al[ac], XmNdeleteResponse, XmDESTROY); ac++; /* for Close in control menu */ XtSetArg(al[ac], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); ac++; if(ccs->childOutDepot == TO_CURRENT) { XtSetArg(al[ac], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); ac++; } ccs->progress = XmCreateWorkingDialog(ccs->win->menu_bar, "progress", al, ac); if(ccs->progress == NULL) return; /* should not happen */ XtUnmanageChild(XmMessageBoxGetChild(ccs->progress, XmDIALOG_OK_BUTTON)); XtUnmanageChild(XmMessageBoxGetChild(ccs->progress, XmDIALOG_HELP_BUTTON)); XtAddCallback(ccs->progress, XmNcancelCallback, (XtCallbackProc)progressStopCB, NULL); XtAddCallback(ccs->progress, XmNdestroyCallback, (XtCallbackProc)progressDestroyCB, (XtPointer)ccs); XtManageChild(ccs->progress); XFlush(XtDisplay(ccs->progress)); /**** XtPopup(XtParent(ccs->progress), XtGrabNone); ****/ } if(ccs->in.fd > 0) { /* only attempt to show progress if the input pipe is opened */ if(ccs->in.len > 0) { #ifdef SHOW_PROGRESS_ON_INPUT_PIPE /* well, for most applications the prg percentage is a very crude estimate; it is mainly because the child standard input is usually buffered (4096 bytes typically); so starting from version 1.3 we don't use that at all (as standard) ; we just show the working dialog with a user suplied message; */ Arg al[2]; /* arg list */ register int ac = 0; /* arg count */ int prg; char work[80]; XmString xmstr; prg = 100.0 * ccs->in.pos/(float)ccs->in.len; sprintf(work, "Done in %d %s", prg, "%"); xmstr = XmStringCreateLtoR(work, charset); XtSetArg(al[ac], XmNmessageString, xmstr); ac++; XtSetValues(ccs->progress, al, ac); XmStringFree(xmstr); /* free memory allocated for XmString */ #endif ; /* don't do anything in 1.3 here (unless you want) */ } } ccs->intervalId = XtAppAddTimeOut(app_context, (unsigned long)lstr.progressInterval, showCmdProgress, (XtPointer)ccs); } /* showCmdProgress */ #ifdef _NO_PROTO Boolean executeShellCmd(win, cmd, cmdInput, left, right, childOutDepot, dlgTitle, dialogsTitle, noOutDialog, noOutMsg) aseditWindowStruct *win; char *cmd; char *cmdInput; XmTextPosition left; XmTextPosition right; int childOutDepot; char *dlgTitle; char *dialogsTitle; int noOutDialog; XmString noOutMsg; #else /* ! _NO_PROTO */ Boolean executeShellCmd(aseditWindowStruct *win, char *cmd, char *cmdInput, XmTextPosition left, XmTextPosition right, int childOutDepot, char *dlgTitle, char *dialogsTitle, int noOutDialog, XmString noOutMsg) #endif { pid_t childpid; size_t len; int inPipe[2], outPipe[2], errPipe[2]; childCommStruct *ccs = NULL; unsigned long delay_interval; XtAppContext app_context = XtWidgetToApplicationContext(win->menu_bar); /* first call forkAndPipe ... */ len = strlen(cmd); if(!len) return False; /* empty command; shouldn't happen */ if((childpid = forkAndPipe(win->menu_bar, cmd, inPipe, outPipe, errPipe)) < 0) { char work[256]; /* To FINISH OFF !!!! strcpy(work, (char *)lstr.unable_to_fork); show_error_message(win, work); */ /* error SHOW it to the user .... */ fprintf(stderr, "\n asedit: ERROR in forkAndPipe (Can't create pipes or fork another process!)"); return False; } else { /* fork was OK */ /* allocate memory for the childCommStruct */ ccs = (childCommStruct *) XtMalloc(sizeof(childCommStruct)); /* store the basic parameters */ ccs->childpid = childpid; ccs->win = win; /* the parent win structure */ ccs->childOutDepot = childOutDepot; ccs->dlgTitle = dlgTitle; ccs->dialogsTitle = dialogsTitle; ccs->noOutDialog = noOutDialog; ccs->noOutMsg = noOutMsg; ccs->left = left; ccs->right = right; /* set the back-pointers */ ccs->in.ccs = (void *)ccs; ccs->out.ccs = (void *)ccs; ccs->err.ccs = (void *)ccs; /* set the pbufLast to NULL pointer in all pipes */ ccs->in.pbufLast = (partialBufStruct *) NULL; ccs->out.pbufLast = (partialBufStruct *) NULL; ccs->err.pbufLast = (partialBufStruct *) NULL; /* set the txt to NULL pointers in all pipes */ ccs->in.txt = (char *)NULL; ccs->out.txt = (char *)NULL; ccs->err.txt = (char *)NULL; /* set the file descriptors for the pipes (parent end) */ ccs->out.fd = outPipe[0]; ccs->err.fd = errPipe[0]; ccs->in.fd = inPipe[1]; /* set non-blocking operations on all pipes (!! compulsory if we don't want to block the whole application !! */ set_nonblock(ccs->in.fd); set_nonblock(ccs->out.fd); set_nonblock(ccs->err.fd); /* set the command input */ ccs->in.txt = cmdInput; if(cmdInput != NULL) ccs->in.len = strlen(cmdInput); else ccs->in.len = 0; ccs->in.pos = 0; /* nothing has been written yet */ ccs->abortedByUser = False; /* set the abort flag */ /* register functions to handle output from the child process */ /* if you don't have app_context handy use: XtWidgetToApplicationContext(w) */ XtAppAddInput(app_context, outPipe[0], (XtPointer) XtInputReadMask, (XtInputCallbackProc)childOutErrPipeCB, (XtPointer)(&ccs->out)); XtAppAddInput(app_context, errPipe[0], (XtPointer) XtInputReadMask, (XtInputCallbackProc)childOutErrPipeCB, (XtPointer)(&ccs->err)); if(ccs->in.len) /* register the callback for the input pipe */ XtAppAddInput(app_context, inPipe[1], (XtPointer) XtInputWriteMask, (XtInputCallbackProc)childInPipeCB, (XtPointer)(&ccs->in)); else { /* nothing to write; close the inPipe[1] and do NOT register any callback */ close(inPipe[1]); ccs->in.fd = -1; /* CLOSED */ } ccs->progress = NULL; /* the widget is not created yet */ /* register a timeout procedure to display a working dialog if the Shell command takes a while to complete */ /* if the output is to the current window show the dialog immediately and make it PRIMARY MODAL */ if(ccs->childOutDepot == TO_CURRENT) delay_interval = (unsigned long)lstr.progressShortInitialDelay; else delay_interval = (unsigned long)lstr.progressInitialDelay; ccs->intervalId = XtAppAddTimeOut(app_context, delay_interval, showCmdProgress, (XtPointer)ccs); } return True; } /* executeShellCmd */ #ifdef _NO_PROTO static int stringToValueCvt(s, stable, values, table_size, default_string, default_value) char *s; char *stable[]; int values[]; int table_size; char *default_string; int default_value; #else /* ! _NO_PROTO */ static int stringToValueCvt(char *s, char *stable[], int values[], int table_size, char *default_string, int default_value) #endif { /* returns a value from values[i] when s equals stable[i] (case insensitive); stable MUST be specified in upper case; both stable and values must be of table_size size; if s is not equal any of the entries in stable returns default_value; Note: this procedure changes s to uppercase! */ int value = default_value; /* when s is NULL or a value was not found */ if(s != NULL) { int i, n, len, len2; len = strlen(s); if(len) /* do not process empty strings */ { /* change all to upper case*/ for(i=0; i< len; i++) s[i] = toupper((unsigned char)s[i]); /* get the value from the values table when strings are the same */ for(i=0; i< table_size; i++) { len2 = strlen(stable[i]); /* only compare if the string is long enough */ if(len >= len2 && strncmp(s, stable[i], len2) == 0) { value = values[i]; break; } } if(i == table_size) /* string was not found; show error */ { fprintf(stderr, "\nError in stringToValueCvt: failed to convert '%s' (in uppercase) to a value", s); fprintf(stderr, "\nUsing the default value for %s", default_string); } } } return value; } static char *depotNames[] = { "TO_NONE", "TO_STDOUT", "TO_STDERR", "TO_CURRENT", "TO_NEW", "TO_TEXT_DIALOG"}; static int depotValues[] = { TO_NONE, TO_STDOUT, TO_STDERR, TO_CURRENT, TO_NEW, TO_TEXT_DIALOG}; static char *dialogNames[] = { "DIALOG_ERROR", "DIALOG_INFORMATION", "DIALOG_MESSAGE", "DIALOG_WARNING" }; static int dialogType[] = { XmDIALOG_ERROR, XmDIALOG_INFORMATION, XmDIALOG_MESSAGE, XmDIALOG_WARNING }; enum extendSelectionValue { NO_EXTEND, EXTEND_TO_LEFT_LF, EXTEND_TO_RIGHT_LF, EXTEND_TO_LINES, EXTEND_TO_WHOLE_FILE, USE_WHOLE_FILE, USE_FILE_OR_SELECTION }; static char *extendNames[] = { "NO_EXTEND", "EXTEND_TO_LEFT_LF", "EXTEND_TO_RIGHT_LF", "EXTEND_TO_LINES", "EXTEND_TO_WHOLE_FILE", "USE_WHOLE_FILE", "USE_FILE_OR_SELECTION" }; static int extendValues[]= { NO_EXTEND, EXTEND_TO_LEFT_LF, EXTEND_TO_RIGHT_LF, EXTEND_TO_LINES, EXTEND_TO_WHOLE_FILE, USE_WHOLE_FILE, USE_FILE_OR_SELECTION }; #ifdef _NO_PROTO void decodeFilterExtensions(filterExt, needSelection, childOutDepot, outDialogTitle, dialogsTitle, noOutDialog, noOutMsg, extendTo) XmStringTable filterExt; Boolean needSelection; int *childOutDepot; char **outDialogTitle; char **dialogsTitle; int *noOutDialog; XmString *noOutMsg; int *extendTo; #else /* ! _NO_PROTO */ void decodeFilterExtensions(XmStringTable filterExt, Boolean needSelection, int *childOutDepot, char **outDialogTitle, char **dialogsTitle, int *noOutDialog, XmString *noOutMsg, int *extendTo) #endif { char *txtPar1=NULL, *txtPar2=NULL; char *txtPar3=NULL; /* {NO}EXTEND_TO_LF */ Boolean stringTableEOF = False; Boolean extendToLF = True; /* as default we extend the selection to the right line feed */ /* set the default values first */ *outDialogTitle = *dialogsTitle= NULL; *noOutMsg = NULL; *childOutDepot=TO_CURRENT; /* default value */ *noOutDialog = TO_NONE; /* = 0 */ /* process (decode) the current table (specified as a resource)*/ #define getString_n_checkEOF(nr, str) if(!stringTableEOF) \ { \ XmStringGetLtoR(filterExt[nr], charset, &str); \ if(!str) stringTableEOF = True; \ } #define getXmString_n_checkEOF(nr, str) if(!stringTableEOF) \ { \ if(filterExt[nr]) str = filterExt[nr]; \ else stringTableEOF = True; \ } /* the structure of filterExt is: childOutDepot (int obtained from text comparison), outDialogTitle (text - to safeguard us from multi-line xm strings), dialogsTitle (text - to safeguard us from multi-line xm strings), noOutDialog (int obtained from text comparison), noOutMsg (xmstr) */ getString_n_checkEOF(0,txtPar1); getString_n_checkEOF(1,*outDialogTitle); getString_n_checkEOF(2,*dialogsTitle); getString_n_checkEOF(3,txtPar2); getXmString_n_checkEOF(4,*noOutMsg); /* Note this is XmString here ! */ getString_n_checkEOF(5,txtPar3); *childOutDepot = stringToValueCvt(txtPar1, depotNames, depotValues, XtNumber(depotNames), "TO_CURRENT", TO_CURRENT); if(!txtPar1) XtFree(txtPar1); *noOutDialog = stringToValueCvt(txtPar2, dialogNames, dialogType, XtNumber(dialogNames), "TO_NONE", TO_NONE); if(!txtPar2) XtFree(txtPar2); /* default value for extendTo depends on needSelection flag */ if(needSelection) *extendTo = stringToValueCvt(txtPar3, extendNames, extendValues, XtNumber(extendNames), "EXTEND_TO_LINES", EXTEND_TO_LINES); else *extendTo = stringToValueCvt(txtPar3, extendNames, extendValues, XtNumber(extendNames), "NO_EXTEND", NO_EXTEND); if(!txtPar3) XtFree(txtPar3); } #ifdef _NO_PROTO void executeFilterCmd(win, cmd, filterExt, needSelection) aseditWindowStruct *win; char *cmd; XmStringTable filterExt; Boolean needSelection; #else /* ! _NO_PROTO */ void executeFilterCmd(aseditWindowStruct *win, char *cmd, XmStringTable filterExt, Boolean needSelection) #endif { char *cmdInput=NULL; int n; XmTextPosition left, right; int childOutDepot, noOutDialog, extendTo; char *outDialogTitle=NULL, *dialogsTitle=NULL; XmString noOutMsg; Boolean ext_status; /* make sure that the cmd is not NULL; */ if(cmd == NULL || strlen(cmd)== 0) return; /* do not process empty command */ /* get the command extended parameters first (if any); we have to do it before checking the needSelection because of the new WHOLE_FILE* flags */ decodeFilterExtensions(filterExt, needSelection, &childOutDepot, &outDialogTitle, &dialogsTitle, &noOutDialog, &noOutMsg, &extendTo); /* the following two checks would probably never be used when needSelection is set to True; appropriate commands should be greyed out when there is no primary selection in the edit area (so this procedure would NOT be called when there is no selection and needSelection is True!) */ if(needSelection && extendTo != EXTEND_TO_WHOLE_FILE && extendTo != USE_WHOLE_FILE && extendTo != USE_FILE_OR_SELECTION ) { /* classic filter case: check the selection and get the selection positions */ if(!XmTextGetSelectionPosition(win->edit_text, &left, &right) || left == right) { if(outDialogTitle) XtFree(outDialogTitle); if(dialogsTitle) XtFree(dialogsTitle); return; /* nothing to process, return */ } } else { /* usually the command case; we don't need the selection and we don't use it as the command input; but get the left and right position of the selection if it does exist; the command result will replace current selection (if it is not available or its length is zero, we will put it at the current insertion point) */ if(!XmTextGetSelectionPosition(win->edit_text, &left, &right) || left==right) left = right = XmTextGetInsertionPosition(win->edit_text); } /* first extend the selection to encompass the last line feed (if not explicitly forbidden) ; alternatively we could extend the selection to whole lines; the important bit is to have the final line feed (sed works on complete lines!) Note we support also extend jut to the beginning of line (although right now I can't think of application that would use that) */ ext_status = True; if (extendTo == EXTEND_TO_RIGHT_LF) ext_status = extendSelectionToLF(win, &left, &right); else if (extendTo == EXTEND_TO_LEFT_LF) ext_status = extendSelectionToBOL(win, &left, &right); else if (extendTo == EXTEND_TO_LINES) ext_status = extendSelectionToLines(win, &left, &right); else if (extendTo == EXTEND_TO_WHOLE_FILE) ext_status = extendSelectionToWholeFile(win, &left, &right); if(!ext_status) /* check the extent status */ { /* we couldn't extend and the user answered "Do NOT proceed" -- return */ if(outDialogTitle) XtFree(outDialogTitle); if(dialogsTitle) XtFree(dialogsTitle); return; } /* a user can request to use the whole file (disregard the selection) */ if(extendTo == USE_WHOLE_FILE) { cmdInput = XmTextGetString(win->edit_text); } else if(extendTo == USE_FILE_OR_SELECTION) { if(left == right) /* selection was empty, use the whole file */ cmdInput = XmTextGetString(win->edit_text); else cmdInput = XmTextGetSelection(win->edit_text); } else { /* default, pre 1.32 situation */ if(needSelection) /* get the selection */ cmdInput = XmTextGetSelection(win->edit_text); else cmdInput = NULL; } if(executeShellCmd(win, cmd, cmdInput, left, right, childOutDepot, outDialogTitle, dialogsTitle, noOutDialog, noOutMsg)) { /* the shell command is being executed */ ; } /* do *NOT* free cmdInput, outDialogTitle, dialogsTitle; their memory will be freed when all input is processed (i.e. in processChildOutput) */ } /* executeFilterCmd */