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