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