diff --git a/src/engine/execcmd.c b/src/engine/execcmd.c index daca0bbc9..f751cbff3 100644 --- a/src/engine/execcmd.c +++ b/src/engine/execcmd.c @@ -70,6 +70,29 @@ void argv_from_shell( char const * * argv, LIST * shell, char const * command, } +/* Returns whether the given command string contains lines longer than the given + * maximum. + */ +int check_cmd_for_too_long_lines( char const * command, int const max, + int * const error_length, int * const error_max_length ) +{ + while ( *command ) + { + size_t const l = strcspn( command, "\n" ); + if ( l > max ) + { + *error_length = l; + *error_max_length = max; + return EXEC_CHECK_LINE_TOO_LONG; + } + command += l; + if ( *command ) + ++command; + } + return EXEC_CHECK_OK; +} + + /* Checks whether the given shell list is actually a request to execute raw * commands without an external shell. */ diff --git a/src/engine/execcmd.h b/src/engine/execcmd.h index 9698ce290..0247f6621 100644 --- a/src/engine/execcmd.h +++ b/src/engine/execcmd.h @@ -42,6 +42,19 @@ typedef void (* ExecCmdCallback) #define EXEC_CMD_FAIL 1 #define EXEC_CMD_INTR 2 +int exec_check +( + string * command, + LIST * * pShell, + int * error_length, + int * error_max_length +); + +/* exec_check() return codes. */ +#define EXEC_CHECK_OK 101 +#define EXEC_CHECK_LINE_TOO_LONG 102 +#define EXEC_CHECK_TOO_LONG 103 + void exec_cmd ( string const * command, @@ -80,5 +93,10 @@ int interrupted( void ); */ int is_raw_command_request( LIST * shell ); +/* Utility worker for exec_check() checking whether all the given command lines + * are under the specified length limit. + */ +int check_cmd_for_too_long_lines( char const * command, int const max, + int * const error_length, int * const error_max_length ); #endif diff --git a/src/engine/execnt.c b/src/engine/execnt.c index df687ace5..5dc4733bf 100644 --- a/src/engine/execnt.c +++ b/src/engine/execnt.c @@ -48,13 +48,13 @@ * Do not just set JAMSHELL to cmd.exe - it will not work! * * External routines: + * exec_check() - preprocess and validate the command. * exec_cmd() - launch an async command execution. * exec_wait() - wait for any of the async command processes to terminate. */ /* get the maximum shell command line length according to the OS */ -int maxline(); - +static int maxline(); /* trim leading and trailing whitespace */ void string_new_trimmed( string * pResult, string const * source ); /* is the command suitable for direct execution via CreateProcessA() */ @@ -187,8 +187,76 @@ void execnt_unit_test() } +/* + * exec_check() - preprocess and validate the command. + */ + +int exec_check +( + string * command, + LIST * * pShell, + int * error_length, + int * error_max_length +) +{ + /* Trim all leading and trailing leading whitespace. */ + string cmd_local[ 1 ]; + string_new_trimmed( cmd_local, command ); + + /* Check prerequisites for executing raw commands. + * + * JAMSHELL setting of "%", indicates that the command should be invoked + * directly if it satisfies all the spawnability criteria or using a batch + * file and the default shell if not. + */ + if ( is_raw_command_request( *pShell ) ) + { + int const raw_cmd_length = can_spawn( cmd_local->value ); + if ( raw_cmd_length < maxline() ) + { + /* Fallback to default shell. */ + list_free( *pShell ); + *pShell = L0; + } + else if ( raw_cmd_length > MAX_RAW_COMMAND_LENGTH ) + { + *error_length = raw_cmd_length; + *error_max_length = MAX_RAW_COMMAND_LENGTH; + string_free( cmd_local ); + return EXEC_CHECK_TOO_LONG; + } + else + { + string_free( cmd_local ); + return EXEC_CHECK_OK; + } + } + + /* Now we know we are using an external shell. Note that there is no need to + * check for too long command strings when using an external shell since we + * use a command file and assume no one is going to set up a JAMSHELL format + * string longer than a few hundred bytes at most which should be well under + * the total command string limit. Should someone actually construct such a + * JAMSHELL value it will get reported as an 'invalid parameter' + * CreateProcessA() Windows API failure which seems like a good enough + * result for such intentional mischief. + */ + + /* Check for too long command lines. */ + { + int const result = check_cmd_for_too_long_lines( cmd_local->value, + maxline(), error_length, error_max_length ); + string_free( cmd_local ); + return result; + } +} + + /* * exec_cmd() - launch an async command execution. + * + * We assume exec_check() already verified that the given command can have its + * command string constructed as requested. */ void exec_cmd @@ -202,7 +270,7 @@ void exec_cmd ) { int const slot = get_free_cmdtab_slot(); - int is_raw_cmd = is_raw_command_request( shell ); + int const is_raw_cmd = is_raw_command_request( shell ); string cmd_local[ 1 ]; /* Initialize default shell - anything more than /Q/C is non-portable. */ @@ -213,21 +281,6 @@ void exec_cmd /* Trim all leading and trailing leading whitespace. */ string_new_trimmed( cmd_local, cmd_orig ); - /* Check to see if we need to hack around the line-length limitation. Look - * for a JAMSHELL setting of "%", indicating that the command should be - * invoked directly. - */ - if ( is_raw_cmd ) - { - /* Check to see if we need to hack around the line-length limitation. - * JAMSHELL setting of "%", indicates that the command should be invoked - * directly if it satisfies all the spawnability criteria or using a - * batch file and the default shell if not. - */ - is_raw_cmd = can_spawn( cmd_local->value ) >= MAXLINE; - shell = L0; - } - /* Specifying no shell means requesting the default shell. */ if ( list_empty( shell ) ) shell = default_shell; @@ -295,14 +348,6 @@ void exec_cmd string_copy( cmdtab[ slot ].command, cmd_orig->value ); } - if ( cmd_local->size > MAX_RAW_COMMAND_LENGTH ) - { - printf( "Command line too long (%d characters). Maximum executable " - "command-line length is %d.", cmd_local->size, - MAX_RAW_COMMAND_LENGTH ); - exit( EXITBAD ); - } - /* Invoke the actual external process using the constructed command line. */ invoke_cmd( cmd_local->value, slot ); @@ -507,7 +552,7 @@ static int raw_maxline() return 996; /* NT 3.5.1 */ } -int maxline() +static int maxline() { static result; if ( !result ) result = raw_maxline(); diff --git a/src/engine/execunix.c b/src/engine/execunix.c index 84a10fe38..f0da51ed7 100644 --- a/src/engine/execunix.c +++ b/src/engine/execunix.c @@ -53,6 +53,7 @@ * Do not just set JAMSHELL to /bin/sh - it will not work! * * External routines: + * exec_check() - preprocess and validate the command. * exec_cmd() - launch an async command execution. * exec_wait() - wait for any of the async command processes to terminate. */ @@ -97,6 +98,25 @@ static struct } cmdtab[ MAXJOBS ] = { { 0 } }; +/* + * exec_check() - preprocess and validate the command. + */ + +int exec_check +( + string * command, + LIST * * pShell, + int * error_length, + int * error_max_length +) +{ + return is_raw_command_request( *pShell ) + ? EXEC_CHECK_OK + : check_cmd_for_too_long_lines( command->value, MAXLINE, error_length, + error_max_length ); +} + + /* * exec_cmd() - launch an async command execution. */ diff --git a/src/engine/jam.h b/src/engine/jam.h index 1781877ad..c689bd526 100644 --- a/src/engine/jam.h +++ b/src/engine/jam.h @@ -46,10 +46,7 @@ #define OSMINOR "OS=NT" #define OS_NT #define SPLITPATH ';' -/* Windows NT 3.51 only allows 996 chars per line, but we deal with the problem - * in "execnt.c". - */ -#define MAXLINE (maxline()) /* longest 'together' actions */ +#define MAXLINE (undefined__see_execnt_c) /* max chars per command line */ #define USE_EXECNT #define USE_PATHUNIX #define PATH_DELIM '\\' @@ -93,7 +90,7 @@ #define OSMINOR "OS=MINGW" #define OS_NT #define SPLITPATH ';' -#define MAXLINE 996 /* longest 'together' actions */ +#define MAXLINE 996 /* max chars per command line */ #define USE_EXECUNIX #define USE_PATHUNIX #define PATH_DELIM '\\' @@ -115,7 +112,7 @@ #ifdef _AIX #define unix - #define MAXLINE 23552 /* 24k - 1k, longest 'together' actions */ + #define MAXLINE 23552 /* 24k - 1k, max chars per command line */ #define OSMINOR "OS=AIX" #define OS_AIX #define NO_VFORK @@ -219,7 +216,7 @@ #define OSMINOR "OS=QNX" #define OS_QNX #define NO_VFORK - #define MAXLINE 996 + #define MAXLINE 996 /* max chars per command line */ #endif #endif #ifdef NeXT @@ -397,7 +394,7 @@ */ #ifndef MAXLINE - #define MAXLINE 102400 /* longest 'together' actions' */ + #define MAXLINE 102400 /* max chars per command line */ #endif #ifndef EXITOK diff --git a/src/engine/make1.c b/src/engine/make1.c index a63ada0f4..160144ae9 100644 --- a/src/engine/make1.c +++ b/src/engine/make1.c @@ -1045,7 +1045,11 @@ static CMD * make1cmds( TARGET * t ) do { CMD * cmd; - int line_too_long = 0; + int cmd_check_result; + int cmd_error_length; + int cmd_error_max_length; + int retry = 0; + int accept_command = 0; /* Build cmd: cmd_new() takes ownership of its lists. */ if ( list_empty( cmd_targets ) ) cmd_targets = list_copy( nt ); @@ -1053,31 +1057,49 @@ static CMD * make1cmds( TARGET * t ) cmd = cmd_new( rule, cmd_targets, list_sublist( ns, start, chunk ), cmd_shell ); - /* Check for too long command string lines. */ - if ( !is_raw_command_request( cmd->shell ) ) + cmd_check_result = exec_check( cmd->buf, &cmd->shell, + &cmd_error_length, &cmd_error_max_length ); + + if ( cmd_check_result == EXEC_CHECK_OK ) { - char * s = cmd->buf->value; - while ( *s ) - { - size_t const l = strcspn( s, "\n" ); - if ( l > MAXLINE ) - { - line_too_long = 1; - break; - } - s += l; - if ( *s ) - ++s; - } + accept_command = 1; + } + else if ( ( actions->flags & RULE_PIECEMEAL ) && ( chunk > 1 ) ) + { + /* Too long but splittable. Reduce chunk size slowly and + * retry. + */ + assert( cmd_check_result == EXEC_CHECK_TOO_LONG || + cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); + chunk = chunk * 9 / 10; + retry = 1; + } + else + { + /* Too long and not splittable. */ + char const * const error_message = cmd_check_result == + EXEC_CHECK_TOO_LONG + ? "is too long" + : "contains a line that is too long"; + assert( cmd_check_result == EXEC_CHECK_TOO_LONG || + cmd_check_result == EXEC_CHECK_LINE_TOO_LONG ); + printf( "%s action %s (%d, max %d):\n", object_str( + rule->name ), error_message, cmd_error_length, + cmd_error_max_length ); + + /* Tell the user what did not fit. */ + fputs( cmd->buf->value, stdout ); + exit( EXITBAD ); } - if ( !line_too_long ) + assert( !retry || !accept_command ); + + if ( accept_command ) { /* Chain it up. */ if ( !cmds ) cmds = cmd; else cmds->tail->next = cmd; cmds->tail = cmd; - start += chunk; /* Mark lists we need recreated for the next command since * they got consumed by the cmd object. @@ -1087,28 +1109,15 @@ static CMD * make1cmds( TARGET * t ) } else { - if ( ( actions->flags & RULE_PIECEMEAL ) && ( chunk > 1 ) ) - { - /* Reduce chunk size slowly. */ - chunk = chunk * 9 / 10; - } - else - { - /* Too long and not splittable. */ - printf( "%s action is too long (max %d):\n", object_str( - rule->name ), MAXLINE ); - - /* Tell the user what did not fit. */ - fputs( cmd->buf->value, stdout ); - exit( EXITBAD ); - } - - /* We can reuse targets & shell lists for the next command. + /* We can reuse targets & shell lists for the next command * if we do not let them die with this cmd object. */ cmd_release_targets_and_shell( cmd ); cmd_free( cmd ); } + + if ( !retry ) + start += chunk; } while ( start < length ); }