source: trunk/third/xmh/command.c @ 15093

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