command.c revision 66d665a3
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(const char* text)
71{
72    char msg[BUFSIZ];
73    snprintf( msg, sizeof(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(const char *str)
82{
83    static char result[100];
84    snprintf(result, sizeof(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 * const *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 = XtMallocArray(
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 = XtReallocArray(altQueue,
290			    (Cardinal)alt_queue_size, sizeof(XEvent) );
291			eventP = &altQueue[alt_queue_count];
292		    }
293		    else
294			eventP++;
295		}
296		break;
297
298	      default:
299		XtDispatchEvent(eventP);
300	    }
301	}
302	(void) wait(0);
303
304	DEBUG("done\n")
305	subProcessRunning = False;
306	if (output_to_pipe) {
307	    CheckReadFromPipe( status->output_pipe[0],
308			       &status->output_buffer,
309			       &status->output_buf_size,
310			       True
311			      );
312	    *bufP = status->output_buffer;
313	    *lenP = status->output_buf_size;
314	    close( status->output_pipe[0] );
315	    XtRemoveInput( status->output_inputId );
316	}
317	if (status->error_pipe[0]) {
318	    CheckReadFromPipe( status->error_pipe[0],
319			       &status->error_buffer,
320			       &status->error_buf_size,
321			       True
322			      );
323	    close( status->error_pipe[0] );
324	    XtRemoveInput( status->error_inputId );
325	}
326	if (status->error_buffer != NULL) {
327	    /* special case for arbitrary shell commands: capture command */
328	    if ((strcmp(argv[0], "/bin/sh") == 0) &&
329	        (strcmp(argv[1], "-c") == 0)) {
330	        status->shell_command = XtNewString(argv[2]);
331            } else status->shell_command = (char*) NULL;
332
333	    while (status->error_buffer[status->error_buf_size-1]  == '\0')
334		status->error_buf_size--;
335	    while (status->error_buffer[status->error_buf_size-1]  == '\n')
336		status->error_buffer[--status->error_buf_size] = '\0';
337	    DEBUG1( "stderr = \"%s\"\n", status->error_buffer )
338	    PopupNotice( status->error_buffer, FreeStatus, (Pointer)status );
339	    return_status = -1;
340	}
341	else {
342	    XtFree( (Pointer)status );
343	    return_status = 0;
344	}
345	for (;alt_queue_count;alt_queue_count--) {
346	    XPutBackEvent(theDisplay, --eventP);
347	}
348	if (type_ahead_count) {
349	    if (alt_queue_size) eventP = &typeAheadQueue[type_ahead_count];
350	    for (;type_ahead_count;type_ahead_count--) {
351		XPutBackEvent(theDisplay, --eventP);
352	    }
353	}
354    } else {			/* We're the child process. */
355	/* take it from the user's path, else fall back to the mhPath */
356	(void) execvp(argv[0], argv);
357	(void) execv(FullPathOfCommand(argv[0]), argv);
358        progName = argv[0];	/* for Punt message */
359	Punt("(cannot execvp it)");
360	return_status = -1;
361    }
362    return return_status;
363}
364
365
366static void
367CheckReadFromPipe(
368    int fd,
369    char **bufP,
370    int *lenP,
371    Bool waitEOF)
372{
373    int nread;
374/*  DEBUG2( " CheckReadFromPipe #%d,len=%d,", fd, *lenP )  */
375#ifdef FIONREAD
376    if (!ioctl( fd, FIONREAD, &nread )) {
377/*      DEBUG1( "nread=%d ...", nread )			   */
378	if (nread) {
379	    int old_end = *lenP;
380	    *bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
381	    read( fd, *bufP+old_end, nread );
382	    (*bufP)[old_end+nread] = '\0';
383	}
384	return;
385    }
386#endif
387    do {
388	char buf[512];
389	int old_end = *lenP;
390	nread = read( fd, buf, 512 );
391	if (nread <= 0)
392	    break;
393	*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
394	memmove( *bufP+old_end, buf, (int) nread );
395	(*bufP)[old_end+nread] = '\0';
396    } while (waitEOF);
397}
398
399
400/* ARGSUSED */
401static void FreeStatus(
402    Widget w,			/* unused */
403    XtPointer closure,
404    XtPointer call_data)	/* unused */
405{
406    CommandStatus status = (CommandStatus)closure;
407    if (status->popup != (Widget)NULL) {
408	XtPopdown( status->popup );
409	XtDestroyWidget( status->popup );
410    }
411    if (status->error_buffer != NULL) XtFree(status->error_buffer);
412    XtFree( closure );
413}
414
415/* Execute the given command, waiting until it's finished.  Put the output
416   in the specified file path.  Returns 0 if stderr empty, -1 otherwise */
417
418int DoCommand(
419  char * const *argv,		/* The command to execute, and its args. */
420  const char *inputfile,	/* Input file for command. */
421  const char *outputfile)	/* Output file for command. */
422{
423    int fd_in, fd_out;
424    int status;
425
426    if (inputfile != NULL) {
427	FILEPTR file = FOpenAndCheck(inputfile, "r");
428	fd_in = dup(fileno(file));
429	myfclose(file);
430    }
431    else
432	fd_in = -1;
433
434    if (outputfile) {
435	FILEPTR file = FOpenAndCheck(outputfile, "w");
436	fd_out = dup(fileno(file));
437	myfclose(file);
438    }
439    else
440	fd_out = -1;
441
442    status = _DoCommandToFileOrPipe( argv, fd_in, fd_out, (char **) NULL,
443				    (int *) NULL );
444    return status;
445}
446
447/* Execute the given command, waiting until it's finished.  Put the output
448   in a newly mallocced string, and return a pointer to that string. */
449
450char *DoCommandToString(char * const *argv)
451{
452    char *result = NULL;
453    int len = 0;
454    _DoCommandToFileOrPipe( argv, -1, -2, &result, &len );
455    if (result == NULL) result = XtMalloc((Cardinal) 1);
456    result[len] = '\0';
457    DEBUG1("('%s')\n", result)
458    return result;
459}
460
461
462/* Execute the command to a temporary file, and return the name of the file. */
463
464char *DoCommandToFile(char * const *argv)
465{
466    char *name;
467    FILEPTR file;
468    int fd;
469    name = MakeNewTempFileName();
470    file = FOpenAndCheck(name, "w");
471    fd = dup(fileno(file));
472    myfclose(file);
473    _DoCommandToFileOrPipe(argv, -1, fd, (char **) NULL, (int *) NULL);
474    return name;
475}
476