/* * Copyright 1993, 1995 Christopher Seiwald. * Copyright 2007 Noel Belcourt. * * This file is part of Jam - see jam.c for Copyright information. */ # include "jam.h" # include "lists.h" # include "execcmd.h" # include "output.h" # include # include # include # include # include /* needed for vfork(), _exit() prototypes */ # include # include #if defined(sun) || defined(__sun) || defined(linux) #include #endif # ifdef USE_EXECUNIX # include # ifdef NO_VFORK # define vfork() fork() # endif /* * execunix.c - execute a shell script on UNIX/WinNT/OS2/AmigaOS * * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). * The default is: * * /bin/sh -c % [ on UNIX/AmigaOS ] * cmd.exe /c % [ on OS2/WinNT ] * * Each word must be an individual element in a jam variable value. * * In $(JAMSHELL), % expands to the command string and ! expands to * the slot number (starting at 1) for multiprocess (-j) invocations. * If $(JAMSHELL) doesn't include a %, it is tacked on as the last * argument. * * Don't just set JAMSHELL to /bin/sh or cmd.exe - it won't work! * * External routines: * execcmd() - launch an async command execution * execwait() - wait and drive at most one execution completion * * Internal routines: * onintr() - bump intr to note command interruption * * 04/08/94 (seiwald) - Coherent/386 support added. * 05/04/94 (seiwald) - async multiprocess interface * 01/22/95 (seiwald) - $(JAMSHELL) support * 06/02/97 (gsar) - full async multiprocess support for Win32 */ static clock_t tps = 0; static struct timeval tv; static int intr = 0; static int cmdsrunning = 0; #define OUT 0 #define ERR 1 static struct { int pid; /* on win32, a real process handle */ int fd[2]; /* file descriptors for stdout and stderr */ FILE *stream[2]; /* child's stdout (0) and stderr (1) file stream */ clock_t start_time; /* start time of child process */ int action_length; /* length of action string */ int target_length; /* length of target string */ char *action; /* buffer to hold action and target invoked */ char *target; /* buffer to hold action and target invoked */ char *command; /* buffer to hold command being invoked */ char *buffer[2]; /* buffer to hold stdout and stderr, if any */ void (*func)( void *closure, int status, timing_info*, char *, char * ); void *closure; } cmdtab[ MAXJOBS ] = {{0}}; /* * onintr() - bump intr to note command interruption */ void onintr( int disp ) { intr++; printf( "...interrupted\n" ); } /* * execcmd() - launch an async command execution */ void execcmd( char *string, void (*func)( void *closure, int status, timing_info*, char *, char * ), void *closure, LIST *shell, char *action, char *target ) { int out[2], err[2]; int slot, len; char *argv[ MAXARGC + 1 ]; /* +1 for NULL */ /* Find a slot in the running commands table for this one. */ for( slot = 0; slot < MAXJOBS; slot++ ) if( !cmdtab[ slot ].pid ) break; if( slot == MAXJOBS ) { printf( "no slots for child!\n" ); exit( EXITBAD ); } /* Forumulate argv */ /* If shell was defined, be prepared for % and ! subs. */ /* Otherwise, use stock /bin/sh (on unix) or cmd.exe (on NT). */ if( shell ) { int i; char jobno[4]; int gotpercent = 0; sprintf( jobno, "%d", slot + 1 ); for( i = 0; shell && i < MAXARGC; i++, shell = list_next( shell ) ) { switch( shell->string[0] ) { case '%': argv[i] = string; gotpercent++; break; case '!': argv[i] = jobno; break; default: argv[i] = shell->string; } if( DEBUG_EXECCMD ) printf( "argv[%d] = '%s'\n", i, argv[i] ); } if( !gotpercent ) argv[i++] = string; argv[i] = 0; } else { argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = string; argv[3] = 0; } /* increment jobs running */ ++cmdsrunning; /* save off actual command string */ cmdtab[ slot ].command = BJAM_MALLOC_ATOMIC(strlen(string)+1); strcpy(cmdtab[slot].command, string); /* create pipe from child to parent */ if (pipe(out) < 0) exit(EXITBAD); fcntl(out[0], F_SETFL, O_NONBLOCK); fcntl(out[1], F_SETFL, O_NONBLOCK); if (pipe(err) < 0) exit(EXITBAD); fcntl(err[0], F_SETFL, O_NONBLOCK); fcntl(err[1], F_SETFL, O_NONBLOCK); /* Start the command */ if (0 < globs.timeout) { struct tms buf; /* * handle hung processes using different mechanism * manually track elapsed time and signal process * when time limit expires * * could use this approach for both consuming too * much cpu and hung processes, but it never hurts * to have backup */ cmdtab[ slot ].start_time = times(&buf); /* make a global, only do this once */ if (tps == 0) tps = sysconf(_SC_CLK_TCK); } if ((cmdtab[slot].pid = vfork()) == 0) { close(out[0]); close(err[0]); dup2(out[1], STDOUT_FILENO); if (globs.pipe_action == 0) { dup2(out[1], STDERR_FILENO); close(err[1]); } else dup2(err[1], STDERR_FILENO); /* terminate processes only if timeout is positive */ if (0 < globs.timeout) { struct rlimit rl; /* * set hard and soft resource limits for cpu usage * won't catch hung processes that don't consume cpu */ rl.rlim_cur = globs.timeout; rl.rlim_max = globs.timeout; setrlimit(RLIMIT_CPU, &rl); } execvp( argv[0], argv ); _exit(127); } else if( cmdtab[slot].pid == -1 ) { perror( "vfork" ); exit( EXITBAD ); } /* close write end of pipes */ close(out[1]); close(err[1]); /* child writes stdout to out[1], parent reads from out[0] */ cmdtab[slot].fd[OUT] = out[0]; cmdtab[slot].stream[OUT] = fdopen(cmdtab[slot].fd[OUT], "rb"); if (cmdtab[slot].stream[OUT] == NULL) { perror( "fdopen" ); exit( EXITBAD ); } /* child writes stderr to err[1], parent reads from err[0] */ if (globs.pipe_action == 0) { close(err[0]); } else { cmdtab[slot].fd[ERR] = err[0]; cmdtab[slot].stream[ERR] = fdopen(cmdtab[slot].fd[ERR], "rb"); if (cmdtab[slot].stream[ERR] == NULL) { perror( "fdopen" ); exit( EXITBAD ); } } /* ensure enough room for rule and target name */ if (action && target) { len = strlen(action) + 1; if (cmdtab[slot].action_length < len) { BJAM_FREE(cmdtab[ slot ].action); cmdtab[ slot ].action = BJAM_MALLOC_ATOMIC(len); cmdtab[ slot ].action_length = len; } strcpy(cmdtab[ slot ].action, action); len = strlen(target) + 1; if (cmdtab[slot].target_length < len) { BJAM_FREE(cmdtab[ slot ].target); cmdtab[ slot ].target = BJAM_MALLOC_ATOMIC(len); cmdtab[ slot ].target_length = len; } strcpy(cmdtab[ slot ].target, target); } else { BJAM_FREE(cmdtab[ slot ].action); BJAM_FREE(cmdtab[ slot ].target); cmdtab[ slot ].action = 0; cmdtab[ slot ].target = 0; cmdtab[ slot ].action_length = 0; cmdtab[ slot ].target_length = 0; } /* Save the operation for execwait() to find. */ cmdtab[ slot ].func = func; cmdtab[ slot ].closure = closure; /* Wait until we're under the limit of concurrent commands. */ /* Don't trust globs.jobs alone. */ while( cmdsrunning >= MAXJOBS || cmdsrunning >= globs.jobs ) if( !execwait() ) break; } /* returns 1 if file is closed, 0 if descriptor is still live * * i is index into cmdtab * * s (stream) indexes * * cmdtab[i].stream[s] * cmdtab[i].buffer[s] and * cmdtab[i].fd[s] */ int read_descriptor(int i, int s) { int ret, len; char buffer[BUFSIZ]; while (0 < (ret = fread(buffer, sizeof(char), BUFSIZ-1, cmdtab[i].stream[s]))) { buffer[ret] = 0; if (!cmdtab[i].buffer[s]) { /* never been allocated */ cmdtab[i].buffer[s] = (char*)BJAM_MALLOC_ATOMIC(ret+1); memcpy(cmdtab[i].buffer[s], buffer, ret+1); } else { /* previously allocated */ char *tmp = cmdtab[i].buffer[s]; len = strlen(tmp); cmdtab[i].buffer[s] = (char*)BJAM_MALLOC_ATOMIC(len+ret+1); memcpy(cmdtab[i].buffer[s], tmp, len); memcpy(cmdtab[i].buffer[s]+len, buffer, ret+1); BJAM_FREE(tmp); } } return feof(cmdtab[i].stream[s]); } void close_streams(int i, int s) { /* close the stream and pipe descriptor */ fclose(cmdtab[i].stream[s]); cmdtab[i].stream[s] = 0; close(cmdtab[i].fd[s]); cmdtab[i].fd[s] = 0; } void populate_file_descriptors(int *fmax, fd_set *fds) { int i, fd_max = 0; /* compute max read file descriptor for use in select */ FD_ZERO(fds); for (i=0; i