📄 shell.c
字号:
kill(- cmdData->childPid, SIGTERM); finishCmdExecution(window, True);}/*** Issue a shell command and feed it the string "input". Output can be** directed either to text widget "textW" where it replaces the text between** the positions "replaceLeft" and "replaceRight", to a separate pop-up dialog** (OUTPUT_TO_DIALOG), or to a macro-language string (OUTPUT_TO_STRING). If** "input" is NULL, no input is fed to the process. If an input string is** provided, it is freed when the command completes. Flags:**** ACCUMULATE Causes output from the command to be saved up until** the command completes.** ERROR_DIALOGS Presents stderr output separately in popup a dialog,** and also reports failed exit status as a popup dialog** including the command output.** REPLACE_SELECTION Causes output to replace the selection in textW.** RELOAD_FILE_AFTER Causes the file to be completely reloaded after the** command completes.** OUTPUT_TO_DIALOG Send output to a pop-up dialog instead of textW** OUTPUT_TO_STRING Output to a macro-language string instead of a text** widget or dialog.**** REPLACE_SELECTION, ERROR_DIALOGS, and OUTPUT_TO_STRING can only be used** along with ACCUMULATE (these operations can't be done incrementally).*/static void issueCommand(WindowInfo *window, const char *command, char *input, int inputLen, int flags, Widget textW, int replaceLeft, int replaceRight, int fromMacro){ int stdinFD, stdoutFD, stderrFD; XtAppContext context = XtWidgetToApplicationContext(window->shell); shellCmdInfo *cmdData; pid_t childPid; /* verify consistency of input parameters */ if ((flags & ERROR_DIALOGS || flags & REPLACE_SELECTION || flags & OUTPUT_TO_STRING) && !(flags & ACCUMULATE)) return; /* a shell command called from a macro must be executed in the same window as the macro, regardless of where the output is directed, so the user can cancel them as a unit */ if (fromMacro) window = MacroRunWindow(); /* put up a watch cursor over the waiting window */ if (!fromMacro) BeginWait(window->shell); /* enable the cancel menu item */ if (!fromMacro) XtSetSensitive(window->cancelShellItem, True); /* fork the subprocess and issue the command */ childPid = forkCommand(window->shell, command, window->path, &stdinFD, &stdoutFD, (flags & ERROR_DIALOGS) ? &stderrFD : NULL); /* set the pipes connected to the process for non-blocking i/o */ if (fcntl(stdinFD, F_SETFL, O_NONBLOCK) < 0) perror("NEdit: Internal error (fcntl)"); if (fcntl(stdoutFD, F_SETFL, O_NONBLOCK) < 0) perror("NEdit: Internal error (fcntl1)"); if (flags & ERROR_DIALOGS) { if (fcntl(stderrFD, F_SETFL, O_NONBLOCK) < 0) perror("NEdit: Internal error (fcntl2)"); } /* if there's nothing to write to the process' stdin, close it now */ if (input == NULL) close(stdinFD); /* Create a data structure for passing process information around amongst the callback routines which will process i/o and completion */ cmdData = (shellCmdInfo *)XtMalloc(sizeof(shellCmdInfo)); window->shellCmdData = cmdData; cmdData->flags = flags; cmdData->stdinFD = stdinFD; cmdData->stdoutFD = stdoutFD; cmdData->stderrFD = stderrFD; cmdData->childPid = childPid; cmdData->outBufs = NULL; cmdData->errBufs = NULL; cmdData->input = input; cmdData->inPtr = input; cmdData->textW = textW; cmdData->bannerIsUp = False; cmdData->fromMacro = fromMacro; cmdData->leftPos = replaceLeft; cmdData->rightPos = replaceRight; cmdData->inLength = inputLen; /* Set up timer proc for putting up banner when process takes too long */ if (fromMacro) cmdData->bannerTimeoutID = 0; else cmdData->bannerTimeoutID = XtAppAddTimeOut(context, BANNER_WAIT_TIME, bannerTimeoutProc, window); /* Set up timer proc for flushing output buffers periodically */ if ((flags & ACCUMULATE) || textW == NULL) cmdData->flushTimeoutID = 0; else cmdData->flushTimeoutID = XtAppAddTimeOut(context, OUTPUT_FLUSH_FREQ, flushTimeoutProc, window); /* set up callbacks for activity on the file descriptors */ cmdData->stdoutInputID = XtAppAddInput(context, stdoutFD, (XtPointer)XtInputReadMask, stdoutReadProc, window); if (input != NULL) cmdData->stdinInputID = XtAppAddInput(context, stdinFD, (XtPointer)XtInputWriteMask, stdinWriteProc, window); else cmdData->stdinInputID = 0; if (flags & ERROR_DIALOGS) cmdData->stderrInputID = XtAppAddInput(context, stderrFD, (XtPointer)XtInputReadMask, stderrReadProc, window); else cmdData->stderrInputID = 0; /* If this was called from a macro, preempt the macro untill shell command completes */ if (fromMacro) PreemptMacro();}/*** Called when the shell sub-process stdout stream has data. Reads data into** the "outBufs" buffer chain in the window->shellCommandData data structure.*/static void stdoutReadProc(XtPointer clientData, int *source, XtInputId *id){ WindowInfo *window = (WindowInfo *)clientData; shellCmdInfo *cmdData = window->shellCmdData; buffer *buf; int nRead; /* read from the process' stdout stream */ buf = (buffer *)XtMalloc(sizeof(buffer)); nRead = read(cmdData->stdoutFD, buf->contents, IO_BUF_SIZE); /* error in read */ if (nRead == -1) { /* error */ if (errno != EWOULDBLOCK && errno != EAGAIN) { perror("NEdit: Error reading shell command output"); XtFree((char *)buf); finishCmdExecution(window, True); } return; } /* end of data. If the stderr stream is done too, execution of the shell process is complete, and we can display the results */ if (nRead == 0) { XtFree((char *)buf); XtRemoveInput(cmdData->stdoutInputID); cmdData->stdoutInputID = 0; if (cmdData->stderrInputID == 0) finishCmdExecution(window, False); return; } /* characters were read successfully, add buf to linked list of buffers */ buf->length = nRead; addOutput(&cmdData->outBufs, buf);}/*** Called when the shell sub-process stderr stream has data. Reads data into** the "errBufs" buffer chain in the window->shellCommandData data structure.*/static void stderrReadProc(XtPointer clientData, int *source, XtInputId *id){ WindowInfo *window = (WindowInfo *)clientData; shellCmdInfo *cmdData = window->shellCmdData; buffer *buf; int nRead; /* read from the process' stderr stream */ buf = (buffer *)XtMalloc(sizeof(buffer)); nRead = read(cmdData->stderrFD, buf->contents, IO_BUF_SIZE); /* error in read */ if (nRead == -1) { if (errno != EWOULDBLOCK && errno != EAGAIN) { perror("NEdit: Error reading shell command error stream"); XtFree((char *)buf); finishCmdExecution(window, True); } return; } /* end of data. If the stdout stream is done too, execution of the shell process is complete, and we can display the results */ if (nRead == 0) { XtFree((char *)buf); XtRemoveInput(cmdData->stderrInputID); cmdData->stderrInputID = 0; if (cmdData->stdoutInputID == 0) finishCmdExecution(window, False); return; } /* characters were read successfully, add buf to linked list of buffers */ buf->length = nRead; addOutput(&cmdData->errBufs, buf);}/*** Called when the shell sub-process stdin stream is ready for input. Writes** data from the "input" text string passed to issueCommand.*/static void stdinWriteProc(XtPointer clientData, int *source, XtInputId *id){ WindowInfo *window = (WindowInfo *)clientData; shellCmdInfo *cmdData = window->shellCmdData; int nWritten; nWritten = write(cmdData->stdinFD, cmdData->inPtr, cmdData->inLength); if (nWritten == -1) { if (errno == EPIPE) { /* Just shut off input to broken pipes. User is likely feeding it to a command which does not take input */ XtRemoveInput(cmdData->stdinInputID); cmdData->stdinInputID = 0; close(cmdData->stdinFD); cmdData->inPtr = NULL; } else if (errno != EWOULDBLOCK && errno != EAGAIN) { perror("NEdit: Write to shell command failed"); finishCmdExecution(window, True); } } else { cmdData->inPtr += nWritten; cmdData->inLength -= nWritten; if (cmdData->inLength <= 0) { XtRemoveInput(cmdData->stdinInputID); cmdData->stdinInputID = 0; close(cmdData->stdinFD); cmdData->inPtr = NULL; } }}/*** Timer proc for putting up the "Shell Command in Progress" banner if** the process is taking too long.*/static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id){ WindowInfo *window = (WindowInfo *)clientData; shellCmdInfo *cmdData = window->shellCmdData; cmdData->bannerIsUp = True; SetModeMessage(window, "Shell Command in Progress -- Press Ctrl+. to Cancel"); cmdData->bannerTimeoutID = 0;}/*** Buffer replacement wrapper routine to be used for inserting output from ** a command into the buffer, which takes into account that the buffer may ** have been shrunken by the user (eg, by Undo). If necessary, the starting** and ending positions (part of the state of the command) are corrected.*/static void safeBufReplace(textBuffer *buf, int *start, int *end, const char *text){ if (*start > buf->length) *start = buf->length; if (*end > buf->length) *end = buf->length; BufReplace(buf, *start, *end, text);}/*** Timer proc for flushing output buffers periodically when the process** takes too long.*/static void flushTimeoutProc(XtPointer clientData, XtIntervalId *id){ WindowInfo *window = (WindowInfo *)clientData; shellCmdInfo *cmdData = window->shellCmdData; textBuffer *buf = TextGetBuffer(cmdData->textW); int len; char *outText; /* shouldn't happen, but it would be bad if it did */ if (cmdData->textW == NULL) return; outText = coalesceOutput(&cmdData->outBufs, &len); if (len != 0) { if (BufSubstituteNullChars(outText, len, buf)) { safeBufReplace(buf, &cmdData->leftPos, &cmdData->rightPos, outText); TextSetCursorPos(cmdData->textW, cmdData->leftPos+strlen(outText)); cmdData->leftPos += len; cmdData->rightPos = cmdData->leftPos; } else fprintf(stderr, "NEdit: Too much binary data\n"); } XtFree(outText); /* re-establish the timer proc (this routine) to continue processing */ cmdData->flushTimeoutID = XtAppAddTimeOut( XtWidgetToApplicationContext(window->shell), OUTPUT_FLUSH_FREQ, flushTimeoutProc, clientData);}/*** Clean up after the execution of a shell command sub-process and present** the output/errors to the user as requested in the initial issueCommand** call. If "terminatedOnError" is true, don't bother trying to read the** output, just close the i/o descriptors, free the memory, and restore the** user interface state.*/static void finishCmdExecution(WindowInfo *window, int terminatedOnError){ shellCmdInfo *cmdData = window->shellCmdData; textBuffer *buf; int status, failure, errorReport, reselectStart, outTextLen, errTextLen; int resp, cancel = False, fromMacro = cmdData->fromMacro; char *outText, *errText = NULL; /* Cancel any pending i/o on the file descriptors */ if (cmdData->stdoutInputID != 0) XtRemoveInput(cmdData->stdoutInputID); if (cmdData->stdinInputID != 0) XtRemoveInput(cmdData->stdinInputID); if (cmdData->stderrInputID != 0) XtRemoveInput(cmdData->stderrInputID); /* Close any file descriptors remaining open */ close(cmdData->stdoutFD); if (cmdData->flags & ERROR_DIALOGS) close(cmdData->stderrFD); if (cmdData->inPtr != NULL) close(cmdData->stdinFD); /* Free the provided input text */ if (cmdData->input != NULL) XtFree(cmdData->input); /* Cancel pending timeouts */ if (cmdData->flushTimeoutID != 0) XtRemoveTimeOut(cmdData->flushTimeoutID); if (cmdData->bannerTimeoutID != 0) XtRemoveTimeOut(cmdData->bannerTimeoutID); /* Clean up waiting-for-shell-command-to-complete mode */ if (!cmdData->fromMacro) { EndWait(window->shell); XtSetSensitive(window->cancelShellItem, False); if (cmdData->bannerIsUp) ClearModeMessage(window); } /* If the process was killed or became inaccessable, give up */ if (terminatedOnError) { freeBufList(&cmdData->outBufs); freeBufList(&cmdData->errBufs); waitpid(cmdData->childPid, &status, 0); goto cmdDone; } /* Assemble the output from the process' stderr and stdout streams into null terminated strings, and free the buffer lists used to collect it */ outText = coalesceOutput(&cmdData->outBufs, &outTextLen); if (cmdData->flags & ERROR_DIALOGS) errText = coalesceOutput(&cmdData->errBufs, &errTextLen); /* Wait for the child process to complete and get its return status */ waitpid(cmdData->childPid, &status, 0); /* Present error and stderr-information dialogs. If a command returned error output, or if the process' exit status indicated failure, present the information to the user. */ if (cmdData->flags & ERROR_DIALOGS) { failure = WIFEXITED(status) && WEXITSTATUS(status) != 0; errorReport = *errText != '\0'; if (failure && errorReport) { removeTrailingNewlines(errText); truncateString(errText, DF_MAX_MSG_LENGTH); resp = DialogF(DF_WARN, window->shell, 2, "Warning", "%s", "Cancel", "Proceed", errText); cancel = resp == 1; } else if (failure) { truncateString(outText, DF_MAX_MSG_LENGTH-70); resp = DialogF(DF_WARN, window->shell, 2, "Command Failure", "Command reported failed exit status.\n" "Output from command:\n%s", "Cancel", "Proceed", outText); cancel = resp == 1; } else if (errorReport) { removeTrailingNewlines(errText); truncateString(errText, DF_MAX_MSG_LENGTH); resp = DialogF(DF_INF, window->shell, 2, "Information", "%s", "Proceed", "Cancel", errText); cancel = resp == 2; } XtFree(errText); if (cancel) { XtFree(outText); goto cmdDone; } } /* If output is to a dialog, present the dialog. Otherwise insert the (remaining) output in the text widget as requested, and move the insert point to the end */ if (cmdData->flags & OUTPUT_TO_DIALOG) { removeTrailingNewlines(outText); if (*outText != '\0') createOutputDialog(window->shell, outText); } else if (cmdData->flags & OUTPUT_TO_STRING) { ReturnShellCommandOutput(window,outText, WEXITSTATUS(status)); } else { buf = TextGetBuffer(cmdData->textW); if (!BufSubstituteNullChars(outText, outTextLen, buf)) { fprintf(stderr,"NEdit: Too much binary data in shell cmd output\n"); outText[0] = '\0'; } if (cmdData->flags & REPLACE_SELECTION) { reselectStart = buf->primary.rectangular ? -1 : buf->primary.start; BufReplaceSelected(buf, outText); TextSetCursorPos(cmdData->textW, buf->cursorPosHint); if (reselectStart != -1) BufSelect(buf, reselectStart, reselectStart + strlen(outText)); } else { safeBufReplace(buf, &cmdData->leftPos, &cmdData->rightPos, outText); TextSetCursorPos(cmdData->textW, cmdData->leftPos+strlen(outText)); } } /* If the command requires the file to be reloaded afterward, reload it */ if (cmdData->flags & RELOAD_FILE_AFTER) RevertToSaved(window); /* Command is complete, free data structure and continue macro execution */ XtFree(outText);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -