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