1/* 2 * Copyright 1993, 1995 Christopher Seiwald. 3 * 4 * This file is part of Jam - see jam.c for Copyright information. 5 */ 6 7/* 8 * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS 9 * 10 * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). 11 * The default is: 12 * 13 * /bin/sh -c % [ on UNIX/AmigaOS ] 14 * cmd.exe /c % [ on OS2/WinNT ] 15 * 16 * Each word must be an individual element in a jam variable value. 17 * 18 * In $(JAMSHELL), % expands to the command string and ! expands to 19 * the slot number (starting at 1) for multiprocess (-j) invocations. 20 * If $(JAMSHELL) doesn't include a %, it is tacked on as the last 21 * argument. 22 * 23 * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! 24 * 25 * External routines: 26 * execcmd() - launch an async command execution 27 * execwait() - wait and drive at most one execution completion 28 * 29 * Internal routines: 30 * onintr() - bump intr to note command interruption 31 * 32 * 04/08/94 (seiwald) - Coherent/386 support added. 33 * 05/04/94 (seiwald) - async multiprocess interface 34 * 01/22/95 (seiwald) - $(JAMSHELL) support 35 * 06/02/97 (gsar) - full async multiprocess support for Win32 36 * 01/20/00 (seiwald) - Upgraded from K&R to ANSI C 37 * 11/04/02 (seiwald) - const-ing for string literals 38 * 12/27/02 (seiwald) - grist .bat file with pid for system uniqueness 39 */ 40 41# include "jam.h" 42# include "lists.h" 43# include "execcmd.h" 44# include <errno.h> 45 46# ifdef USE_EXECUNIX 47 48# ifdef OS_OS2 49# define USE_EXECNT 50# include <process.h> 51# endif 52 53# ifdef unix 54# include <unistd.h> 55# endif 56 57# ifdef OS_NT 58# define USE_EXECNT 59# include <process.h> 60# define WIN32_LEAN_AND_MEAN 61# include <windows.h> /* do the ugly deed */ 62# define USE_MYWAIT 63# if !defined( __BORLANDC__ ) 64# define wait my_wait 65static int my_wait( int *status ); 66# endif 67# endif 68 69static int intr = 0; 70static int cmdsrunning = 0; 71static void (*istat)( int ); 72 73static struct 74{ 75 int pid; /* on win32, a real process handle */ 76 void (*func)( void *closure, int status ); 77 void *closure; 78 79# ifdef USE_EXECNT 80 char *tempfile; 81# endif 82 83} cmdtab[ MAXJOBS ] = {{0}}; 84 85/* 86 * onintr() - bump intr to note command interruption 87 */ 88 89void 90onintr( int disp ) 91{ 92 intr++; 93 printf( "...interrupted\n" ); 94} 95 96/* 97 * execcmd() - launch an async command execution 98 */ 99 100void 101execcmd( 102 char *string, 103 void (*func)( void *closure, int status ), 104 void *closure, 105 LIST *shell ) 106{ 107 int pid; 108 int slot; 109 const char *argv[ MAXARGC + 1 ]; /* +1 for NULL */ 110 111# ifdef USE_EXECNT 112 char *p; 113# endif 114 115 /* Find a slot in the running commands table for this one. */ 116 117 for( slot = 0; slot < MAXJOBS; slot++ ) 118 if( !cmdtab[ slot ].pid ) 119 break; 120 121 if( slot == MAXJOBS ) 122 { 123 printf( "no slots for child!\n" ); 124 exit( EXITBAD ); 125 } 126 127# ifdef USE_EXECNT 128 if( !cmdtab[ slot ].tempfile ) 129 { 130 char *tempdir; 131 132 if( !( tempdir = getenv( "TEMP" ) ) && 133 !( tempdir = getenv( "TMP" ) ) ) 134 tempdir = "\\temp"; 135 136 /* +32 is room for \jamXXXXXtSS.bat (at least) */ 137 138 cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 32 ); 139 140 sprintf( cmdtab[ slot ].tempfile, "%s\\jam%dt%d.bat", 141 tempdir, GetCurrentProcessId(), slot ); 142 } 143 144 /* Trim leading, ending white space */ 145 146 while( isspace( *string ) ) 147 ++string; 148 149 p = strchr( string, '\n' ); 150 151 while( p && isspace( *p ) ) 152 ++p; 153 154 /* If multi line, or too long, or JAMSHELL is set, write to bat file. */ 155 /* Otherwise, exec directly. */ 156 /* Frankly, if it is a single long line I don't think the */ 157 /* command interpreter will do any better -- it will fail. */ 158 159 if( p && *p || strlen( string ) > MAXLINE || shell ) 160 { 161 FILE *f; 162 163 /* Write command to bat file. */ 164 165 f = fopen( cmdtab[ slot ].tempfile, "w" ); 166 fputs( string, f ); 167 fclose( f ); 168 169 string = cmdtab[ slot ].tempfile; 170 } 171# endif 172 173 /* Forumulate argv */ 174 /* If shell was defined, be prepared for % and ! subs. */ 175 /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */ 176 177 if( shell ) 178 { 179 int i; 180 char jobno[4]; 181 int gotpercent = 0; 182 183 sprintf( jobno, "%d", slot + 1 ); 184 185 for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) ) 186 { 187 switch( shell->string[0] ) 188 { 189 case '%': argv[i] = string; gotpercent++; break; 190 case '!': argv[i] = jobno; break; 191 default: argv[i] = shell->string; 192 } 193 if( DEBUG_EXECCMD ) 194 printf( "argv[%d] = '%s'\n", i, argv[i] ); 195 } 196 197 if( !gotpercent ) 198 argv[i++] = string; 199 200 argv[i] = 0; 201 } 202 else 203 { 204# ifdef USE_EXECNT 205 argv[0] = "cmd.exe"; 206 argv[1] = "/Q/C"; /* anything more is non-portable */ 207# else 208 argv[0] = "/bin/sh"; 209 argv[1] = "-c"; 210# endif 211 argv[2] = string; 212 argv[3] = 0; 213 } 214 215 /* Catch interrupts whenever commands are running. */ 216 217 if( !cmdsrunning++ ) 218 istat = signal( SIGINT, onintr ); 219 220 /* Start the command */ 221 222# ifdef USE_EXECNT 223 if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) 224 { 225 perror( "spawn" ); 226 exit( EXITBAD ); 227 } 228# else 229# ifdef NO_VFORK 230 if ((pid = fork()) == 0) 231 { 232 execvp( argv[0], argv ); 233 _exit(127); 234 } 235# else 236 if ((pid = vfork()) == 0) 237 { 238 execvp( argv[0], (char* const*)argv ); 239 _exit(127); 240 } 241# endif 242 243 if( pid == -1 ) 244 { 245 perror( "vfork" ); 246 exit( EXITBAD ); 247 } 248# endif 249 /* Save the operation for execwait() to find. */ 250 251 cmdtab[ slot ].pid = pid; 252 cmdtab[ slot ].func = func; 253 cmdtab[ slot ].closure = closure; 254 255 /* Wait until we're under the limit of concurrent commands. */ 256 /* Don't trust globs.jobs alone. */ 257 258 while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs ) 259 if( !execwait() ) 260 break; 261} 262 263/* 264 * execwait() - wait and drive at most one execution completion 265 */ 266 267int 268execwait() 269{ 270 int i; 271 int status, w; 272 int rstat; 273 274 /* Handle naive make1() which doesn't know if cmds are running. */ 275 276 if( !cmdsrunning ) 277 return 0; 278 279 do 280 { 281 /* Pick up process pid and status */ 282 283 while( ( w = wait( &status ) ) == -1 && errno == EINTR ) 284 ; 285 286 if( w == -1 ) 287 { 288 printf( "child process(es) lost!\n" ); 289 perror("wait"); 290 exit( EXITBAD ); 291 } 292 293 /* Find the process in the cmdtab. */ 294 295 for( i = 0; i < MAXJOBS; i++ ) 296 if( w == cmdtab[ i ].pid ) 297 break; 298 299 if( i == MAXJOBS ) 300 printf( "jam: waif child process %d found, ignoring it!\n", w ); 301 } while( i == MAXJOBS ); 302 303# ifdef USE_EXECNT 304 /* Clear the temp file */ 305 306 unlink( cmdtab[ i ].tempfile ); 307# endif 308 309 /* Drive the completion */ 310 311 if( !--cmdsrunning ) 312 signal( SIGINT, istat ); 313 314 if( intr ) 315 rstat = EXEC_CMD_INTR; 316 else if( w == -1 || status != 0 ) 317 rstat = EXEC_CMD_FAIL; 318 else 319 rstat = EXEC_CMD_OK; 320 321 cmdtab[ i ].pid = 0; 322 323 (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); 324 325 return 1; 326} 327 328# ifdef USE_MYWAIT 329 330static int 331my_wait( int *status ) 332{ 333 int i, num_active = 0; 334 DWORD exitcode, waitcode; 335 static HANDLE *active_handles = 0; 336 337 if (!active_handles) 338 active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) ); 339 340 /* first see if any non-waited-for processes are dead, 341 * and return if so. 342 */ 343 for ( i = 0; i < globs.jobs; i++ ) { 344 if ( cmdtab[i].pid ) { 345 if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) { 346 if ( exitcode == STILL_ACTIVE ) 347 active_handles[num_active++] = (HANDLE)cmdtab[i].pid; 348 else { 349 CloseHandle((HANDLE)cmdtab[i].pid); 350 *status = (int)((exitcode & 0xff) << 8); 351 return cmdtab[i].pid; 352 } 353 } 354 else 355 goto FAILED; 356 } 357 } 358 359 /* if a child exists, wait for it to die */ 360 if ( !num_active ) { 361 errno = ECHILD; 362 return -1; 363 } 364 waitcode = WaitForMultipleObjects( num_active, 365 active_handles, 366 FALSE, 367 INFINITE ); 368 if ( waitcode != WAIT_FAILED ) { 369 if ( waitcode >= WAIT_ABANDONED_0 370 && waitcode < WAIT_ABANDONED_0 + num_active ) 371 i = waitcode - WAIT_ABANDONED_0; 372 else 373 i = waitcode - WAIT_OBJECT_0; 374 if ( GetExitCodeProcess(active_handles[i], &exitcode) ) { 375 CloseHandle(active_handles[i]); 376 *status = (int)((exitcode & 0xff) << 8); 377 return (int)active_handles[i]; 378 } 379 } 380 381FAILED: 382 errno = GetLastError(); 383 return -1; 384 385} 386 387# endif /* USE_MYWAIT */ 388 389# endif /* USE_EXECUNIX */ 390