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