command.c revision c9e2be55
1/* $XConsortium: command.c,v 2.49 95/04/05 19:59:06 kaleb Exp $ */
2/* $XFree86: xc/programs/xmh/command.c,v 3.8 2001/12/09 15:48:36 herrb Exp $ */
3
4/*
5 *			  COPYRIGHT 1987, 1989
6 *		   DIGITAL EQUIPMENT CORPORATION
7 *		       MAYNARD, MASSACHUSETTS
8 *			ALL RIGHTS RESERVED.
9 *
10 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
11 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
12 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
13 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
14 *
15 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
16 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
17 * ADDITION TO THAT SET FORTH ABOVE.
18 *
19 *
20 * Permission to use, copy, modify, and distribute this software and its
21 * documentation for any purpose and without fee is hereby granted, provided
22 * that the above copyright notice appear in all copies and that both that
23 * copyright notice and this permission notice appear in supporting
24 * documentation, and that the name of Digital Equipment Corporation not be
25 * used in advertising or publicity pertaining to distribution of the software
26 * without specific, written prior permission.
27 */
28
29/* command.c -- interface to exec mh commands. */
30
31#include "xmh.h"
32#include <X11/Xpoll.h>
33#include <sys/ioctl.h>
34#include <signal.h>
35#ifndef SYSV
36#include <sys/wait.h>
37#endif	/* SYSV */
38#if defined(SVR4) && !defined(DGUX)
39#include <sys/filio.h>
40#endif
41
42/* number of user input events to queue before malloc */
43#define TYPEAHEADSIZE 20
44
45#ifndef HAS_VFORK
46#define vfork() fork()
47#else
48#if defined(sun) && !defined(SVR4)
49#include <vfork.h>
50#endif
51#endif
52
53typedef struct _CommandStatus {
54    Widget	popup;		 /* must be first; see PopupStatus */
55    struct _LastInput lastInput; /* must be second; ditto */
56    char*	shell_command;	 /* must be third; for XmhShellCommand */
57    int		child_pid;
58    XtInputId	output_inputId;
59    XtInputId	error_inputId;
60    int		output_pipe[2];
61    int		error_pipe[2];
62    char*	output_buffer;
63    int		output_buf_size;
64    char*	error_buffer;
65    int		error_buf_size;
66} CommandStatusRec, *CommandStatus;
67
68typedef char* Pointer;
69static void FreeStatus(XMH_CB_ARGS);
70static void CheckReadFromPipe(int, char **, int *, Bool);
71
72static void SystemError(char* text)
73{
74    char msg[BUFSIZ];
75    sprintf( msg, "%s; errno = %d %s", text, errno,
76	     strerror(errno));
77    XtWarning( msg );
78}
79
80
81/* Return the full path name of the given mh command. */
82
83static char *FullPathOfCommand(char *str)
84{
85    static char result[100];
86    (void) sprintf(result, "%s/%s", app_resources.mh_path, str);
87    return result;
88}
89
90
91/*ARGSUSED*/
92static void ReadStdout(
93    XtPointer closure,
94    int *fd,
95    XtInputId *id)	/* unused */
96{
97    register CommandStatus status = (CommandStatus)closure;
98    CheckReadFromPipe(*fd, &status->output_buffer, &status->output_buf_size,
99		      False);
100}
101
102
103/*ARGSUSED*/
104static void ReadStderr(
105    XtPointer closure,
106    int *fd,
107    XtInputId *id)	/* unused */
108{
109    register CommandStatus status = (CommandStatus)closure;
110    CheckReadFromPipe(*fd, &status->error_buffer, &status->error_buf_size,
111		      False);
112}
113
114
115static volatile int childdone;		/* Gets nonzero when the child process
116				   finishes. */
117/* ARGSUSED */
118static void
119ChildDone(int n)
120{
121    childdone++;
122}
123
124/* Execute the given command, and wait until it has finished.  While the
125   command is executing, watch the X socket and cause Xlib to read in any
126   incoming data.  This will prevent the socket from overflowing during
127   long commands.  Returns 0 if stderr empty, -1 otherwise. */
128
129static int _DoCommandToFileOrPipe(
130  char **argv,			/* The command to execute, and its args. */
131  int inputfd,			/* Input stream for command. */
132  int outputfd,			/* Output stream; /dev/null if == -1 */
133  char **bufP,			/* output buffer ptr if outputfd == -2 */
134  int *lenP)			/* output length ptr if outputfd == -2 */
135{
136    XtAppContext appCtx = XtWidgetToApplicationContext(toplevel);
137    int return_status;
138    int old_stdin = 0, old_stdout = 0, old_stderr = 0;
139    int pid;
140    fd_set readfds, fds;
141    Boolean output_to_pipe = False;
142    CommandStatus status = XtNew(CommandStatusRec);
143    FD_ZERO(&fds);
144    FD_SET(ConnectionNumber(theDisplay), &fds);
145    DEBUG1("Executing %s ...", argv[0])
146
147    if (inputfd != -1) {
148	old_stdin = dup(fileno(stdin));
149	(void) dup2(inputfd, fileno(stdin));
150	close(inputfd);
151    }
152
153    if (outputfd == -1) {
154	if (!app_resources.debug) { /* Throw away stdout. */
155	    outputfd = open( "/dev/null", O_WRONLY, 0 );
156	}
157    }
158    else if (outputfd == -2) {	/* make pipe */
159	if (pipe(status->output_pipe) /*failed*/) {
160	    SystemError( "couldn't re-direct standard output" );
161	    status->output_pipe[0]=0;
162	}
163	else {
164	    outputfd = status->output_pipe[1];
165	    FD_SET(status->output_pipe[0], &fds);
166	    status->output_inputId =
167		XtAppAddInput( appCtx,
168			   status->output_pipe[0], (XtPointer)XtInputReadMask,
169			   ReadStdout, (XtPointer)status
170			     );
171	    status->output_buffer = NULL;
172	    status->output_buf_size = 0;
173	    output_to_pipe = True;
174	}
175    }
176
177    if (pipe(status->error_pipe) /*failed*/) {
178	SystemError( "couldn't re-direct standard error" );
179	status->error_pipe[0]=0;
180    }
181    else {
182	old_stderr = dup(fileno(stderr));
183	(void) dup2(status->error_pipe[1], fileno(stderr));
184	close(status->error_pipe[1]);
185	FD_SET(status->error_pipe[0], &fds);
186	status->error_inputId =
187	    XtAppAddInput( appCtx,
188			   status->error_pipe[0], (XtPointer)XtInputReadMask,
189			   ReadStderr, (XtPointer)status
190			  );
191    }
192    if (outputfd != -1) {
193	old_stdout = dup(fileno(stdout));
194	(void) dup2(outputfd, fileno(stdout));
195	close(outputfd);
196    }
197    childdone = FALSE;
198    status->popup = (Widget)NULL;
199    status->lastInput = lastInput;
200    status->error_buffer = NULL;
201    status->error_buf_size = 0;
202    (void) signal(SIGCHLD, ChildDone);
203    pid = vfork();
204    if (inputfd != -1) {
205	if (pid != 0) dup2(old_stdin,  fileno(stdin));
206	close(old_stdin);
207    }
208    if (outputfd != -1) {
209	if (pid != 0) dup2(old_stdout, fileno(stdout));
210	close(old_stdout);
211    }
212    if (status->error_pipe[0]) {
213	if (pid != 0) dup2(old_stderr, fileno(stderr));
214	close(old_stderr);
215    }
216
217    if (pid == -1) Punt("Couldn't fork!");
218    if (pid) {			/* We're the parent process. */
219	XEvent typeAheadQueue[TYPEAHEADSIZE], *eventP = typeAheadQueue;
220	XEvent *altQueue = NULL;
221	int type_ahead_count = 0, alt_queue_size = 0, alt_queue_count = 0;
222	XtAppContext app = XtWidgetToApplicationContext(toplevel);
223	int num_fds = ConnectionNumber(theDisplay)+1;
224	if (output_to_pipe && status->output_pipe[0] >= num_fds)
225	    num_fds = status->output_pipe[0]+1;
226	if (status->error_pipe[0] >= num_fds)
227	    num_fds = status->error_pipe[0]+1;
228	status->child_pid = pid;
229	DEBUG1( " pid=%d ", pid )
230	subProcessRunning = True;
231	while (!childdone) {
232	    while (!(XtAppPending(app) & XtIMXEvent)) {
233		/* this is gross, but the only other way is by
234		 * polling on timers or an extra pipe, since we're not
235		 * guaranteed to be able to malloc in a signal handler.
236		 */
237		readfds = fds;
238                if (childdone) break;
239		DEBUG("blocking.\n")
240		(void) Select(num_fds, &readfds, NULL, NULL, NULL);
241		DEBUG1("unblocked; child%s done.\n", childdone ? "" : " not")
242		if (childdone) break;
243		if (!FD_ISSET(ConnectionNumber(theDisplay), &readfds)) {
244		    DEBUG("reading alternate input...")
245		    XtAppProcessEvent(appCtx, (unsigned)XtIMAlternateInput);
246		    DEBUG("read.\n")
247		}
248	    }
249	    if (childdone) break;
250	    XtAppNextEvent(app, eventP);
251	    switch(eventP->type) {
252	      case LeaveNotify:
253		if (type_ahead_count) {
254		    /* do compress_enterleave here to save memory */
255		    XEvent *prevEvent;
256		    if (alt_queue_size && (alt_queue_count == 0))
257			prevEvent = &typeAheadQueue[type_ahead_count-1];
258		    else
259			prevEvent = eventP - 1;
260		    if (prevEvent->type == EnterNotify
261		      && prevEvent->xany.display == eventP->xany.display
262		      && prevEvent->xany.window == eventP->xany.window) {
263			eventP = prevEvent;
264			if (alt_queue_count > 0)
265			    alt_queue_count--;
266			else
267			    type_ahead_count--;
268			break;
269		    }
270		}
271		/* fall through */
272	      case KeyPress:
273	      case KeyRelease:
274	      case EnterNotify:
275	      case ButtonPress:
276	      case ButtonRelease:
277	      case MotionNotify:
278		if (type_ahead_count < TYPEAHEADSIZE) {
279		    if (++type_ahead_count == TYPEAHEADSIZE) {
280			altQueue = (XEvent*)XtMalloc(
281				(Cardinal)TYPEAHEADSIZE*sizeof(XEvent) );
282			alt_queue_size = TYPEAHEADSIZE;
283			eventP = altQueue;
284		    }
285		    else
286			eventP++;
287		}
288		else {
289		    if (++alt_queue_count == alt_queue_size) {
290			alt_queue_size += TYPEAHEADSIZE;
291			altQueue = (XEvent*)XtRealloc(
292				(char*)altQueue,
293				(Cardinal)alt_queue_size*sizeof(XEvent) );
294			eventP = &altQueue[alt_queue_count];
295		    }
296		    else
297			eventP++;
298		}
299		break;
300
301	      default:
302		XtDispatchEvent(eventP);
303	    }
304	}
305	(void) wait(0);
306
307	DEBUG("done\n")
308	subProcessRunning = False;
309	if (output_to_pipe) {
310	    CheckReadFromPipe( status->output_pipe[0],
311			       &status->output_buffer,
312			       &status->output_buf_size,
313			       True
314			      );
315	    *bufP = status->output_buffer;
316	    *lenP = status->output_buf_size;
317	    close( status->output_pipe[0] );
318	    XtRemoveInput( status->output_inputId );
319	}
320	if (status->error_pipe[0]) {
321	    CheckReadFromPipe( status->error_pipe[0],
322			       &status->error_buffer,
323			       &status->error_buf_size,
324			       True
325			      );
326	    close( status->error_pipe[0] );
327	    XtRemoveInput( status->error_inputId );
328	}
329	if (status->error_buffer != NULL) {
330	    /* special case for arbitrary shell commands: capture command */
331	    if ((strcmp(argv[0], "/bin/sh") == 0) &&
332	        (strcmp(argv[1], "-c") == 0)) {
333	        status->shell_command = XtNewString(argv[2]);
334            } else status->shell_command = (char*) NULL;
335
336	    while (status->error_buffer[status->error_buf_size-1]  == '\0')
337		status->error_buf_size--;
338	    while (status->error_buffer[status->error_buf_size-1]  == '\n')
339		status->error_buffer[--status->error_buf_size] = '\0';
340	    DEBUG1( "stderr = \"%s\"\n", status->error_buffer )
341	    PopupNotice( status->error_buffer, FreeStatus, (Pointer)status );
342	    return_status = -1;
343	}
344	else {
345	    XtFree( (Pointer)status );
346	    return_status = 0;
347	}
348	for (;alt_queue_count;alt_queue_count--) {
349	    XPutBackEvent(theDisplay, --eventP);
350	}
351	if (type_ahead_count) {
352	    if (alt_queue_size) eventP = &typeAheadQueue[type_ahead_count];
353	    for (;type_ahead_count;type_ahead_count--) {
354		XPutBackEvent(theDisplay, --eventP);
355	    }
356	}
357    } else {			/* We're the child process. */
358	/* take it from the user's path, else fall back to the mhPath */
359	(void) execvp(argv[0], argv);
360	(void) execv(FullPathOfCommand(argv[0]), argv);
361        progName = argv[0];	/* for Punt message */
362	Punt("(cannot execvp it)");
363	return_status = -1;
364    }
365    return return_status;
366}
367
368
369static void
370CheckReadFromPipe(
371    int fd,
372    char **bufP,
373    int *lenP,
374    Bool waitEOF)
375{
376    int nread;
377/*  DEBUG2( " CheckReadFromPipe #%d,len=%d,", fd, *lenP )  */
378#ifdef FIONREAD
379    if (!ioctl( fd, FIONREAD, &nread )) {
380/*      DEBUG1( "nread=%d ...", nread )			   */
381	if (nread) {
382	    int old_end = *lenP;
383	    *bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
384	    read( fd, *bufP+old_end, nread );
385	    (*bufP)[old_end+nread] = '\0';
386	}
387	return;
388    }
389#endif
390    do {
391	char buf[512];
392	int old_end = *lenP;
393	nread = read( fd, buf, 512 );
394	if (nread <= 0)
395	    break;
396	*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
397	memmove( *bufP+old_end, buf, (int) nread );
398	(*bufP)[old_end+nread] = '\0';
399    } while (waitEOF);
400}
401
402
403/* ARGSUSED */
404static void FreeStatus(
405    Widget w,			/* unused */
406    XtPointer closure,
407    XtPointer call_data)	/* unused */
408{
409    CommandStatus status = (CommandStatus)closure;
410    if (status->popup != (Widget)NULL) {
411	XtPopdown( status->popup );
412	XtDestroyWidget( status->popup );
413    }
414    if (status->error_buffer != NULL) XtFree(status->error_buffer);
415    XtFree( closure );
416}
417
418/* Execute the given command, waiting until it's finished.  Put the output
419   in the specified file path.  Returns 0 if stderr empty, -1 otherwise */
420
421int DoCommand(
422  char **argv,			/* The command to execute, and its args. */
423  char *inputfile,		/* Input file for command. */
424  char *outputfile)		/* Output file for command. */
425{
426    int fd_in, fd_out;
427    int status;
428
429    if (inputfile != NULL) {
430	FILEPTR file = FOpenAndCheck(inputfile, "r");
431	fd_in = dup(fileno(file));
432	myfclose(file);
433    }
434    else
435	fd_in = -1;
436
437    if (outputfile) {
438	FILEPTR file = FOpenAndCheck(outputfile, "w");
439	fd_out = dup(fileno(file));
440	myfclose(file);
441    }
442    else
443	fd_out = -1;
444
445    status = _DoCommandToFileOrPipe( argv, fd_in, fd_out, (char **) NULL,
446				    (int *) NULL );
447    return status;
448}
449
450/* Execute the given command, waiting until it's finished.  Put the output
451   in a newly mallocced string, and return a pointer to that string. */
452
453char *DoCommandToString(char ** argv)
454{
455    char *result = NULL;
456    int len = 0;
457    _DoCommandToFileOrPipe( argv, -1, -2, &result, &len );
458    if (result == NULL) result = XtMalloc((Cardinal) 1);
459    result[len] = '\0';
460    DEBUG1("('%s')\n", result)
461    return result;
462}
463
464
465/* Execute the command to a temporary file, and return the name of the file. */
466
467char *DoCommandToFile(char **argv)
468{
469    char *name;
470    FILEPTR file;
471    int fd;
472    name = MakeNewTempFileName();
473    file = FOpenAndCheck(name, "w");
474    fd = dup(fileno(file));
475    myfclose(file);
476    _DoCommandToFileOrPipe(argv, -1, fd, (char **) NULL, (int *) NULL);
477    return name;
478}
479