diff options
Diffstat (limited to 'xc/programs/xmh/command.c')
-rw-r--r-- | xc/programs/xmh/command.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/xc/programs/xmh/command.c b/xc/programs/xmh/command.c new file mode 100644 index 000000000..d42bf19d5 --- /dev/null +++ b/xc/programs/xmh/command.c @@ -0,0 +1,483 @@ +/* $XConsortium: command.c,v 2.49 95/04/05 19:59:06 kaleb Exp $ */ +/* $XFree86: xc/programs/xmh/command.c,v 3.5 1998/12/13 07:37:52 dawes Exp $ */ + +/* + * COPYRIGHT 1987, 1989 + * DIGITAL EQUIPMENT CORPORATION + * MAYNARD, MASSACHUSETTS + * ALL RIGHTS RESERVED. + * + * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND + * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION. + * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR + * ANY PURPOSE. IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. + * + * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT + * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN + * ADDITION TO THAT SET FORTH ABOVE. + * + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and 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, and that the name of Digital Equipment Corporation not be + * used in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. + */ + +/* command.c -- interface to exec mh commands. */ + +#include "xmh.h" +#include <X11/Xpoll.h> +#include <sys/ioctl.h> +#include <signal.h> +#ifndef SYSV +#include <sys/wait.h> +#endif /* SYSV */ +#if defined(SVR4) && !defined(SCO325) && !defined(DGUX) +#include <sys/filio.h> +#endif + +/* number of user input events to queue before malloc */ +#define TYPEAHEADSIZE 20 + +#ifndef HAS_VFORK +#define vfork() fork() +#else +#if defined(sun) && !defined(SVR4) +#include <vfork.h> +#endif +#endif + +typedef struct _CommandStatus { + Widget popup; /* must be first; see PopupStatus */ + struct _LastInput lastInput; /* must be second; ditto */ + char* shell_command; /* must be third; for XmhShellCommand */ + int child_pid; + XtInputId output_inputId; + XtInputId error_inputId; + int output_pipe[2]; + int error_pipe[2]; + char* output_buffer; + int output_buf_size; + char* error_buffer; + int error_buf_size; +} CommandStatusRec, *CommandStatus; + +typedef char* Pointer; +static void FreeStatus(); +static void CheckReadFromPipe(); + +static void SystemError(text) + char* text; +{ + char msg[BUFSIZ]; + sprintf( msg, "%s; errno = %d %s", text, errno, + strerror(errno)); + XtWarning( msg ); +} + + +/* Return the full path name of the given mh command. */ + +static char *FullPathOfCommand(str) + char *str; +{ + static char result[100]; + (void) sprintf(result, "%s/%s", app_resources.mh_path, str); + return result; +} + + +/*ARGSUSED*/ +static void ReadStdout(closure, fd, id) + XtPointer closure; + int *fd; + XtInputId *id; /* unused */ +{ + register CommandStatus status = (CommandStatus)closure; + CheckReadFromPipe(*fd, &status->output_buffer, &status->output_buf_size, + False); +} + + +/*ARGSUSED*/ +static void ReadStderr(closure, fd, id) + XtPointer closure; + int *fd; + XtInputId *id; /* unused */ +{ + register CommandStatus status = (CommandStatus)closure; + CheckReadFromPipe(*fd, &status->error_buffer, &status->error_buf_size, + False); +} + + +static int childdone; /* Gets nonzero when the child process + finishes. */ +/* ARGSUSED */ +static void +ChildDone(n) + int n; +{ + childdone++; +} + +/* Execute the given command, and wait until it has finished. While the + command is executing, watch the X socket and cause Xlib to read in any + incoming data. This will prevent the socket from overflowing during + long commands. Returns 0 if stderr empty, -1 otherwise. */ + +static int _DoCommandToFileOrPipe(argv, inputfd, outputfd, bufP, lenP) + char **argv; /* The command to execute, and its args. */ + int inputfd; /* Input stream for command. */ + int outputfd; /* Output stream; /dev/null if == -1 */ + char **bufP; /* output buffer ptr if outputfd == -2 */ + int *lenP; /* output length ptr if outputfd == -2 */ +{ + XtAppContext appCtx = XtWidgetToApplicationContext(toplevel); + int return_status; + int old_stdin, old_stdout, old_stderr; + int pid; + fd_set readfds, fds; + Boolean output_to_pipe = False; + CommandStatus status = XtNew(CommandStatusRec); + FD_ZERO(&fds); + FD_SET(ConnectionNumber(theDisplay), &fds); + DEBUG1("Executing %s ...", argv[0]) + + if (inputfd != -1) { + old_stdin = dup(fileno(stdin)); + (void) dup2(inputfd, fileno(stdin)); + close(inputfd); + } + + if (outputfd == -1) { + if (!app_resources.debug) { /* Throw away stdout. */ + outputfd = open( "/dev/null", O_WRONLY, 0 ); + } + } + else if (outputfd == -2) { /* make pipe */ + if (pipe(status->output_pipe) /*failed*/) { + SystemError( "couldn't re-direct standard output" ); + status->output_pipe[0]=0; + } + else { + outputfd = status->output_pipe[1]; + FD_SET(status->output_pipe[0], &fds); + status->output_inputId = + XtAppAddInput( appCtx, + status->output_pipe[0], (XtPointer)XtInputReadMask, + ReadStdout, (XtPointer)status + ); + status->output_buffer = NULL; + status->output_buf_size = 0; + output_to_pipe = True; + } + } + + if (pipe(status->error_pipe) /*failed*/) { + SystemError( "couldn't re-direct standard error" ); + status->error_pipe[0]=0; + } + else { + old_stderr = dup(fileno(stderr)); + (void) dup2(status->error_pipe[1], fileno(stderr)); + close(status->error_pipe[1]); + FD_SET(status->error_pipe[0], &fds); + status->error_inputId = + XtAppAddInput( appCtx, + status->error_pipe[0], (XtPointer)XtInputReadMask, + ReadStderr, (XtPointer)status + ); + } + if (outputfd != -1) { + old_stdout = dup(fileno(stdout)); + (void) dup2(outputfd, fileno(stdout)); + close(outputfd); + } + childdone = FALSE; + status->popup = (Widget)NULL; + status->lastInput = lastInput; + status->error_buffer = NULL; + status->error_buf_size = 0; + (void) signal(SIGCHLD, ChildDone); + pid = vfork(); + if (inputfd != -1) { + if (pid != 0) dup2(old_stdin, fileno(stdin)); + close(old_stdin); + } + if (outputfd != -1) { + if (pid != 0) dup2(old_stdout, fileno(stdout)); + close(old_stdout); + } + if (status->error_pipe[0]) { + if (pid != 0) dup2(old_stderr, fileno(stderr)); + close(old_stderr); + } + + if (pid == -1) Punt("Couldn't fork!"); + if (pid) { /* We're the parent process. */ + XEvent typeAheadQueue[TYPEAHEADSIZE], *eventP = typeAheadQueue; + XEvent *altQueue = NULL; + int type_ahead_count = 0, alt_queue_size = 0, alt_queue_count = 0; + XtAppContext app = XtWidgetToApplicationContext(toplevel); + int num_fds = ConnectionNumber(theDisplay)+1; + if (output_to_pipe && status->output_pipe[0] >= num_fds) + num_fds = status->output_pipe[0]+1; + if (status->error_pipe[0] >= num_fds) + num_fds = status->error_pipe[0]+1; + status->child_pid = pid; + DEBUG1( " pid=%d ", pid ) + subProcessRunning = True; + while (!childdone) { + while (!(XtAppPending(app) & XtIMXEvent)) { + /* this is gross, but the only other way is by + * polling on timers or an extra pipe, since we're not + * guaranteed to be able to malloc in a signal handler. + */ + readfds = fds; + if (childdone) break; +DEBUG("blocking.\n") + (void) Select(num_fds, &readfds, NULL, NULL, NULL); +DEBUG1("unblocked; child%s done.\n", childdone ? "" : " not") + if (childdone) break; + if (!FD_ISSET(ConnectionNumber(theDisplay), &readfds)) +{DEBUG("reading alternate input...") + XtAppProcessEvent(appCtx, (unsigned)XtIMAlternateInput); +DEBUG("read.\n")} + } + if (childdone) break; + XtAppNextEvent(app, eventP); + switch(eventP->type) { + case LeaveNotify: + if (type_ahead_count) { + /* do compress_enterleave here to save memory */ + XEvent *prevEvent; + if (alt_queue_size && (alt_queue_count == 0)) + prevEvent = &typeAheadQueue[type_ahead_count-1]; + else + prevEvent = eventP - 1; + if (prevEvent->type == EnterNotify + && prevEvent->xany.display == eventP->xany.display + && prevEvent->xany.window == eventP->xany.window) { + eventP = prevEvent; + if (alt_queue_count > 0) + alt_queue_count--; + else + type_ahead_count--; + break; + } + } + /* fall through */ + case KeyPress: + case KeyRelease: + case EnterNotify: + case ButtonPress: + case ButtonRelease: + case MotionNotify: + if (type_ahead_count < TYPEAHEADSIZE) { + if (++type_ahead_count == TYPEAHEADSIZE) { + altQueue = (XEvent*)XtMalloc( + (Cardinal)TYPEAHEADSIZE*sizeof(XEvent) ); + alt_queue_size = TYPEAHEADSIZE; + eventP = altQueue; + } + else + eventP++; + } + else { + if (++alt_queue_count == alt_queue_size) { + alt_queue_size += TYPEAHEADSIZE; + altQueue = (XEvent*)XtRealloc( + (char*)altQueue, + (Cardinal)alt_queue_size*sizeof(XEvent) ); + eventP = &altQueue[alt_queue_count]; + } + else + eventP++; + } + break; + + default: + XtDispatchEvent(eventP); + } + } + (void) wait(0); + + DEBUG("done\n") + subProcessRunning = False; + if (output_to_pipe) { + CheckReadFromPipe( status->output_pipe[0], + &status->output_buffer, + &status->output_buf_size, + True + ); + *bufP = status->output_buffer; + *lenP = status->output_buf_size; + close( status->output_pipe[0] ); + XtRemoveInput( status->output_inputId ); + } + if (status->error_pipe[0]) { + CheckReadFromPipe( status->error_pipe[0], + &status->error_buffer, + &status->error_buf_size, + True + ); + close( status->error_pipe[0] ); + XtRemoveInput( status->error_inputId ); + } + if (status->error_buffer != NULL) { + /* special case for arbitrary shell commands: capture command */ + if ((strcmp(argv[0], "/bin/sh") == 0) && + (strcmp(argv[1], "-c") == 0)) { + status->shell_command = XtNewString(argv[2]); + } else status->shell_command = (char*) NULL; + + while (status->error_buffer[status->error_buf_size-1] == '\0') + status->error_buf_size--; + while (status->error_buffer[status->error_buf_size-1] == '\n') + status->error_buffer[--status->error_buf_size] = '\0'; + DEBUG1( "stderr = \"%s\"\n", status->error_buffer ) + PopupNotice( status->error_buffer, FreeStatus, (Pointer)status ); + return_status = -1; + } + else { + XtFree( (Pointer)status ); + return_status = 0; + } + for (;alt_queue_count;alt_queue_count--) { + XPutBackEvent(theDisplay, --eventP); + } + if (type_ahead_count) { + if (alt_queue_size) eventP = &typeAheadQueue[type_ahead_count]; + for (;type_ahead_count;type_ahead_count--) { + XPutBackEvent(theDisplay, --eventP); + } + } + } else { /* We're the child process. */ + /* take it from the user's path, else fall back to the mhPath */ + (void) execvp(argv[0], argv); + (void) execv(FullPathOfCommand(argv[0]), argv); + progName = argv[0]; /* for Punt message */ + Punt("(cannot execvp it)"); + return_status = -1; + } + return return_status; +} + + +static void +CheckReadFromPipe( fd, bufP, lenP, waitEOF ) + int fd; + char **bufP; + int *lenP; + Bool waitEOF; +{ + int nread; +/* DEBUG2( " CheckReadFromPipe #%d,len=%d,", fd, *lenP ) */ +#ifdef FIONREAD + if (!ioctl( fd, FIONREAD, &nread )) { +/* DEBUG1( "nread=%d ...", nread ) */ + if (nread) { + int old_end = *lenP; + *bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) ); + read( fd, *bufP+old_end, nread ); + (*bufP)[old_end+nread] = '\0'; + } + return; + } +#endif + do { + char buf[512]; + int old_end = *lenP; + nread = read( fd, buf, 512 ); + if (nread <= 0) + break; + *bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) ); + memmove( *bufP+old_end, buf, (int) nread ); + (*bufP)[old_end+nread] = '\0'; + } while (waitEOF); +} + + +/* ARGSUSED */ +static void FreeStatus( w, closure, call_data ) + Widget w; /* unused */ + Pointer closure; + Pointer call_data; /* unused */ +{ + CommandStatus status = (CommandStatus)closure; + if (status->popup != (Widget)NULL) { + XtPopdown( status->popup ); + XtDestroyWidget( status->popup ); + } + if (status->error_buffer != NULL) XtFree(status->error_buffer); + XtFree( closure ); +} + +/* Execute the given command, waiting until it's finished. Put the output + in the specified file path. Returns 0 if stderr empty, -1 otherwise */ + +DoCommand(argv, inputfile, outputfile) + char **argv; /* The command to execute, and its args. */ + char *inputfile; /* Input file for command. */ + char *outputfile; /* Output file for command. */ +{ + int fd_in, fd_out; + int status; + + if (inputfile != NULL) { + FILEPTR file = FOpenAndCheck(inputfile, "r"); + fd_in = dup(fileno(file)); + myfclose(file); + } + else + fd_in = -1; + + if (outputfile) { + FILEPTR file = FOpenAndCheck(outputfile, "w"); + fd_out = dup(fileno(file)); + myfclose(file); + } + else + fd_out = -1; + + status = _DoCommandToFileOrPipe( argv, fd_in, fd_out, (char **) NULL, + (int *) NULL ); + return status; +} + +/* Execute the given command, waiting until it's finished. Put the output + in a newly mallocced string, and return a pointer to that string. */ + +char *DoCommandToString(argv) +char ** argv; +{ + char *result = NULL; + int len = 0; + _DoCommandToFileOrPipe( argv, -1, -2, &result, &len ); + if (result == NULL) result = XtMalloc((Cardinal) 1); + result[len] = '\0'; + DEBUG1("('%s')\n", result) + return result; +} + + +/* Execute the command to a temporary file, and return the name of the file. */ + +char *DoCommandToFile(argv) + char **argv; +{ + char *name; + FILEPTR file; + int fd; + name = MakeNewTempFileName(); + file = FOpenAndCheck(name, "w"); + fd = dup(fileno(file)); + myfclose(file); + _DoCommandToFileOrPipe(argv, -1, fd, (char **) NULL, (int *) NULL); + return name; +} + |