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 | |
---|
67 | typedef 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 | |
---|
82 | typedef char* Pointer; |
---|
83 | static void FreeStatus(); |
---|
84 | static void CheckReadFromPipe(); |
---|
85 | |
---|
86 | static 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 | |
---|
98 | static 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*/ |
---|
108 | static 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*/ |
---|
120 | static 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 | |
---|
131 | static int childdone; /* Gets nonzero when the child process |
---|
132 | finishes. */ |
---|
133 | /* ARGSUSED */ |
---|
134 | static void |
---|
135 | ChildDone(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 | |
---|
146 | static 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; |
---|
256 | DEBUG("blocking.\n") |
---|
257 | (void) select(num_fds, (int *) &readfds, |
---|
258 | (int *) NULL, (int *) NULL, (struct timeval *) NULL); |
---|
259 | DEBUG1("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); |
---|
264 | DEBUG("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 | |
---|
386 | static void |
---|
387 | CheckReadFromPipe( 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 */ |
---|
421 | static 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 | |
---|
438 | DoCommand(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 | |
---|
470 | char *DoCommandToString(argv) |
---|
471 | char ** 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 | |
---|
485 | char *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 | |
---|