/* * Copyright 1993, 1995 Christopher Seiwald. * * This file is part of Jam - see jam.c for Copyright information. */ # include "jam.h" # include "lists.h" # include "execcmd.h" # include <errno.h> # ifdef USE_EXECNT # define WIN32_LEAN_AND_MEAN # include <windows.h> /* do the ugly deed */ # include <process.h> # if !defined( __BORLANDC__ ) && !defined( OS_OS2 ) # define wait my_wait static int my_wait( int *status ); # endif /* * execnt.c - execute a shell command on Windows NT and Windows 95/98 * * If $(JAMSHELL) is defined, uses that to formulate execvp()/spawnvp(). * The default is: * * /bin/sh -c % [ on UNIX/AmigaOS ] * cmd.exe /c % [ on Windows NT ] * * 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 int intr = 0; static int cmdsrunning = 0; static void (*istat)( int ); static int is_nt_351 = 0; static int is_win95 = 1; static int is_win95_defined = 0; static struct { int pid; /* on win32, a real process handle */ void (*func)( void *closure, int status ); void *closure; char *tempfile; } cmdtab[ MAXJOBS ] = {{0}}; static void set_is_win95( void ) { OSVERSIONINFO os_info; os_info.dwOSVersionInfoSize = sizeof(os_info); os_info.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; GetVersionEx( &os_info ); is_win95 = (os_info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS); is_win95_defined = 1; /* now, test wether we're running Windows 3.51 */ /* this is later used to limit the system call command length */ if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT) is_nt_351 = os_info.dwMajorVersion == 3; } static char** string_to_args( const char* string, int* pcount ) { int total = strlen( string ); int in_quote = 0, num_args; char* line; char* p; char** arg; char** args; *pcount = 0; /* do not copy trailing newlines, if any */ { int i; for ( i = total-1; i > 0; i-- ) { if ( string[i] != '\n' && string[i] != '\r' ) break; total --; } } /* first of all, copy the input string */ line = (char*)malloc( total+2 ); if (!line) return 0; memcpy( line+1, string, total ); line[0] = 0; line[total+1] = 0; in_quote = 0; for ( p = line+1; p[0]; p++ ) { switch (p[0]) { case '"': in_quote = !in_quote; break; case ' ': case '\t': if (!in_quote) p[0] = 0; default: ; } } /* now count the arguments.. */ for ( p = line; p < line+total+1; p++ ) if ( !p[0] && p[1] ) num_args++; /* allocate the args array */ args = (char**)malloc( num_args*sizeof(char*)+2 ); if (!args) { free( line ); return 0; } arg = args+1; for ( p = line; p < line+total+1; p++ ) if ( !p[0] && p[1] ) { arg[0] = p+1; arg++; } arg[0] = 0; *pcount = num_args; args[0] = line; return args+1; } static void free_args( char** args ) { free( args[-1] ); free( args-1 ); } /* process a "del" or "erase" command under Windows 95/98 */ static int process_del( char* command ) { char** arg; char* p = command, *q; int wildcard = 0, result = 0; /* first of all, skip the command itself */ if ( p[0] == 'd' ) p += 3; /* assumes "del..;" */ else if ( p[0] == 'e' ) p += 5; /* assumes "erase.." */ else return 1; /* invalid command */ /* process all targets independently */ for (;;) { /* skip leading spaces */ while ( *p && isspace(*p) ) p++; /* exit if we encounter an end of string */ if (!*p) return 0; /* ignore toggles/flags */ if (*p == '/') { p++; while ( *p && isalnum(*p) ) p++; } else { int in_quote = 0; int wildcard = 0; int go_on = 1; q = p; while (go_on) { switch (*p) { case '"': in_quote = !in_quote; break; case '?': case '*': if (!in_quote) wildcard = 1; break; case '\0': if (in_quote) return 1; /* fall-through */ case ' ': case '\t': if (!in_quote) { int len = p - q; int result; char* line; /* q..p-1 contains the delete argument */ if ( len <= 0 ) return 1; line = (char*)malloc( len+4+1 ); if (!line) return 1; strncpy( line, "del ", 4 ); strncpy( line+4, q, len ); line[len+4] = '\0'; if ( wildcard ) result = system( line ); else result = !DeleteFile( line+4 ); free( line ); if (result) return 1; go_on = 0; } default: ; } p++; } /* while (go_on) */ } } } /* * 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 ), void *closure, LIST *shell ) { int pid; int slot; int max_line; char *argv[ MAXARGC + 1 ]; /* +1 for NULL */ char *p; if ( !is_win95_defined ) set_is_win95(); /* Find a slot in the running commands table for this one. */ if ( is_win95 ) { /* only synchronous spans are supported on Windows 95/98 */ slot = 0; } else { for( slot = 0; slot < MAXJOBS; slot++ ) if( !cmdtab[ slot ].pid ) break; } if( slot == MAXJOBS ) { printf( "no slots for child!\n" ); exit( EXITBAD ); } if( !cmdtab[ slot ].tempfile ) { char *tempdir; if( !( tempdir = getenv( "TEMP" ) ) && !( tempdir = getenv( "TMP" ) ) ) tempdir = "\\temp"; cmdtab[ slot ].tempfile = malloc( strlen( tempdir ) + 14 ); sprintf( cmdtab[ slot ].tempfile, "%s\\jamtmp%02d.bat", tempdir, slot ); } /* Trim leading, ending white space */ while( isspace( *string ) ) ++string; p = strchr( string, '\n' ); while( p && isspace( *p ) ) ++p; /* on Windows NT 3.51, the maximul line length is 996 bytes !! */ /* while it's much bigger NT 4 and 2k */ max_line = is_nt_351 ? 996 : MAXLINE; /* If multi line, or too long, or JAMSHELL is set, write to bat file. */ /* Otherwise, exec directly. */ /* Frankly, if it is a single long line I don't think the */ /* command interpreter will do any better -- it will fail. */ if( p && *p || strlen( string ) > max_line || shell ) { FILE *f; /* Write command to bat file. */ f = fopen( cmdtab[ slot ].tempfile, "w" ); fputs( string, f ); fclose( f ); string = cmdtab[ slot ].tempfile; } /* 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 { /* don't worry, this is ignored on Win95/98, see later.. */ argv[0] = "cmd.exe"; argv[1] = "/Q/C"; /* anything more is non-portable */ argv[2] = string; argv[3] = 0; } /* Catch interrupts whenever commands are running. */ if( !cmdsrunning++ ) istat = signal( SIGINT, onintr ); /* Start the command */ /* on Win95, we only do a synchronous call */ if ( is_win95 ) { static const char* hard_coded[] = { "del", "erase", "copy", "mkdir", "rmdir", "cls", "dir", "ren", "rename", "move", 0 }; const char** keyword; int len, spawn = 1; int result; for ( keyword = hard_coded; keyword[0]; keyword++ ) { len = strlen( keyword[0] ); if ( strnicmp( string, keyword[0], len ) == 0 && !isalnum(string[len]) ) { /* this is one of the hard coded symbols, use 'system' to run */ /* them.. except for "del"/"erase" */ if ( keyword - hard_coded < 2 ) result = process_del( string ); else result = system( string ); spawn = 0; break; } } if (spawn) { char** args; int num_args; /* convert the string into an array of arguments */ /* we need to take care of double quotes !! */ args = string_to_args( string, &num_args ); if ( args ) { #if 0 char** arg; fprintf( stderr, "%s: ", args[0] ); arg = args+1; while ( arg[0] ) { fprintf( stderr, " {%s}", arg[0] ); arg++; } fprintf( stderr, "\n" ); #endif result = spawnvp( P_WAIT, args[0], args ); free_args( args ); } else result = 1; } func( closure, result ? EXEC_CMD_FAIL : EXEC_CMD_OK ); return; } /* the rest is for Windows NT only */ if( ( pid = spawnvp( P_NOWAIT, argv[0], argv ) ) == -1 ) { perror( "spawn" ); exit( EXITBAD ); } /* Save the operation for execwait() to find. */ cmdtab[ slot ].pid = pid; 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; } /* * execwait() - wait and drive at most one execution completion */ int execwait() { int i; int status, w; int rstat; /* Handle naive make1() which doesn't know if cmds are running. */ if( !cmdsrunning ) return 0; if ( is_win95 ) return 0; /* Pick up process pid and status */ while( ( w = wait( &status ) ) == -1 && errno == EINTR ) ; if( w == -1 ) { printf( "child process(es) lost!\n" ); perror("wait"); exit( EXITBAD ); } /* Find the process in the cmdtab. */ for( i = 0; i < MAXJOBS; i++ ) if( w == cmdtab[ i ].pid ) break; if( i == MAXJOBS ) { printf( "waif child found!\n" ); exit( EXITBAD ); } /* Drive the completion */ if( !--cmdsrunning ) signal( SIGINT, istat ); if( intr ) rstat = EXEC_CMD_INTR; else if( w == -1 || status != 0 ) rstat = EXEC_CMD_FAIL; else rstat = EXEC_CMD_OK; cmdtab[ i ].pid = 0; (*cmdtab[ i ].func)( cmdtab[ i ].closure, rstat ); return 1; } # if !defined( __BORLANDC__ ) static int my_wait( int *status ) { int i, num_active = 0; DWORD exitcode, waitcode; static HANDLE *active_handles = 0; if (!active_handles) active_handles = (HANDLE *)malloc(globs.jobs * sizeof(HANDLE) ); /* first see if any non-waited-for processes are dead, * and return if so. */ for ( i = 0; i < globs.jobs; i++ ) { if ( cmdtab[i].pid ) { if ( GetExitCodeProcess((HANDLE)cmdtab[i].pid, &exitcode) ) { if ( exitcode == STILL_ACTIVE ) active_handles[num_active++] = (HANDLE)cmdtab[i].pid; else { CloseHandle((HANDLE)cmdtab[i].pid); *status = (int)((exitcode & 0xff) << 8); return cmdtab[i].pid; } } else goto FAILED; } } /* if a child exists, wait for it to die */ if ( !num_active ) { errno = ECHILD; return -1; } waitcode = WaitForMultipleObjects( num_active, active_handles, FALSE, INFINITE ); if ( waitcode != WAIT_FAILED ) { if ( waitcode >= WAIT_ABANDONED_0 && waitcode < WAIT_ABANDONED_0 + num_active ) i = waitcode - WAIT_ABANDONED_0; else i = waitcode - WAIT_OBJECT_0; if ( GetExitCodeProcess(active_handles[i], &exitcode) ) { CloseHandle(active_handles[i]); *status = (int)((exitcode & 0xff) << 8); return (int)active_handles[i]; } } FAILED: errno = GetLastError(); return -1; } # endif /* !__BORLANDC__ */ # endif /* USE_EXECNT */
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#3 | 679 | david_turner |
experimental support for wider MAXLINE values on Windows NT (NT 3.51 is detected at run-time in "execnt.c" and dealt accordingly). |
||
#2 | 621 | david_turner | Win 9x related fixed submitted by Fabio Parodi | ||
#1 | 597 | david_turner |
Adding support for Windows 95/98 through a specific implementation of the "exec" module, named "execnt.c" |