[cmdline] Match user expectations for &&, ||, goto, and exit
The && and || operators should be left-associative, since that is how they are treated in most other languages (including C and Unix shell). For example, in the command: dhcp net0 && goto dhcp_ok || echo No DHCP on net0 if the "dhcp net0" fails then the "echo" should be executed. After an "exit" or a successful "goto", further commands on the same line should never be executed. For example: goto somewhere && echo This should never be printed exit 0 && echo This should never be printed exit 1 && echo This should never be printed An "exit" should cause the current shell or script to terminate and return the specified exit status to its caller. For example: chain test.ipxe && echo Success || echo Failure [in test.ipxe] #!ipxe exit 0 should echo "Success". Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
parent
01df5c510f
commit
7bebe9579e
106
src/core/exec.c
106
src/core/exec.c
@ -31,6 +31,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||||||
#include <ipxe/command.h>
|
#include <ipxe/command.h>
|
||||||
#include <ipxe/parseopt.h>
|
#include <ipxe/parseopt.h>
|
||||||
#include <ipxe/settings.h>
|
#include <ipxe/settings.h>
|
||||||
|
#include <ipxe/shell.h>
|
||||||
|
|
||||||
/** @file
|
/** @file
|
||||||
*
|
*
|
||||||
@ -38,15 +39,15 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Shell exit flag */
|
/** Shell stop state */
|
||||||
int shell_exit;
|
static int stop_state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute command
|
* Execute command
|
||||||
*
|
*
|
||||||
* @v command Command name
|
* @v command Command name
|
||||||
* @v argv Argument list
|
* @v argv Argument list
|
||||||
* @ret rc Command exit status
|
* @ret rc Return status code
|
||||||
*
|
*
|
||||||
* Execute the named command. Unlike a traditional POSIX execv(),
|
* Execute the named command. Unlike a traditional POSIX execv(),
|
||||||
* this function returns the exit status of the command.
|
* this function returns the exit status of the command.
|
||||||
@ -196,32 +197,22 @@ static int split_command ( char *command, char **tokens ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminate command unconditionally
|
* Process next command only if previous command succeeded
|
||||||
*
|
*
|
||||||
* @v rc Status of previous command
|
* @v rc Status of previous command
|
||||||
* @ret terminate Terminate command
|
* @ret process Process next command
|
||||||
*/
|
*/
|
||||||
static int terminate_always ( int rc __unused ) {
|
static int process_on_success ( int rc ) {
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminate command only if previous command succeeded
|
|
||||||
*
|
|
||||||
* @v rc Status of previous command
|
|
||||||
* @ret terminate Terminate command
|
|
||||||
*/
|
|
||||||
static int terminate_on_success ( int rc ) {
|
|
||||||
return ( rc == 0 );
|
return ( rc == 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminate command only if previous command failed
|
* Process next command only if previous command failed
|
||||||
*
|
*
|
||||||
* @v rc Status of previous command
|
* @v rc Status of previous command
|
||||||
* @ret terminate Terminate command
|
* @ret process Process next command
|
||||||
*/
|
*/
|
||||||
static int terminate_on_failure ( int rc ) {
|
static int process_on_failure ( int rc ) {
|
||||||
return ( rc != 0 );
|
return ( rc != 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,54 +220,79 @@ static int terminate_on_failure ( int rc ) {
|
|||||||
* Find command terminator
|
* Find command terminator
|
||||||
*
|
*
|
||||||
* @v tokens Token list
|
* @v tokens Token list
|
||||||
* @ret terminator Terminator type
|
* @ret process_next "Should next command be processed?" function
|
||||||
* @ret argc Argument count
|
* @ret argc Argument count
|
||||||
*/
|
*/
|
||||||
static int command_terminator ( char **tokens,
|
static int command_terminator ( char **tokens,
|
||||||
int ( **terminator ) ( int rc ) ) {
|
int ( **process_next ) ( int rc ) ) {
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
|
||||||
/* Find first terminating token */
|
/* Find first terminating token */
|
||||||
for ( i = 0 ; tokens[i] ; i++ ) {
|
for ( i = 0 ; tokens[i] ; i++ ) {
|
||||||
if ( tokens[i][0] == '#' ) {
|
if ( tokens[i][0] == '#' ) {
|
||||||
/* Start of a comment */
|
/* Start of a comment */
|
||||||
*terminator = terminate_always;
|
break;
|
||||||
return i;
|
|
||||||
} else if ( strcmp ( tokens[i], "||" ) == 0 ) {
|
} else if ( strcmp ( tokens[i], "||" ) == 0 ) {
|
||||||
/* Short-circuit logical OR */
|
/* Short-circuit logical OR */
|
||||||
*terminator = terminate_on_success;
|
*process_next = process_on_failure;
|
||||||
return i;
|
return i;
|
||||||
} else if ( strcmp ( tokens[i], "&&" ) == 0 ) {
|
} else if ( strcmp ( tokens[i], "&&" ) == 0 ) {
|
||||||
/* Short-circuit logical AND */
|
/* Short-circuit logical AND */
|
||||||
*terminator = terminate_on_failure;
|
*process_next = process_on_success;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* End of token list */
|
/* End of token list */
|
||||||
*terminator = terminate_always;
|
*process_next = NULL;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set shell stop state
|
||||||
|
*
|
||||||
|
* @v stop Shell stop state
|
||||||
|
*/
|
||||||
|
void shell_stop ( int stop ) {
|
||||||
|
stop_state = stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test and consume shell stop state
|
||||||
|
*
|
||||||
|
* @v stop Shell stop state to consume
|
||||||
|
* @v stopped Shell had been stopped
|
||||||
|
*/
|
||||||
|
int shell_stopped ( int stop ) {
|
||||||
|
int stopped;
|
||||||
|
|
||||||
|
/* Test to see if we need to stop */
|
||||||
|
stopped = ( stop_state >= stop );
|
||||||
|
|
||||||
|
/* Consume stop state */
|
||||||
|
if ( stop_state <= stop )
|
||||||
|
stop_state = 0;
|
||||||
|
|
||||||
|
return stopped;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute command line
|
* Execute command line
|
||||||
*
|
*
|
||||||
* @v command Command line
|
* @v command Command line
|
||||||
* @ret rc Command exit status
|
* @ret rc Return status code
|
||||||
*
|
*
|
||||||
* Execute the named command and arguments.
|
* Execute the named command and arguments.
|
||||||
*/
|
*/
|
||||||
int system ( const char *command ) {
|
int system ( const char *command ) {
|
||||||
int ( * terminator ) ( int rc );
|
int ( * process_next ) ( int rc );
|
||||||
char *expcmd;
|
char *expcmd;
|
||||||
char **argv;
|
char **argv;
|
||||||
int argc;
|
int argc;
|
||||||
int count;
|
int count;
|
||||||
|
int process;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
/* Reset exit flag */
|
|
||||||
shell_exit = 0;
|
|
||||||
|
|
||||||
/* Perform variable expansion */
|
/* Perform variable expansion */
|
||||||
expcmd = expand_command ( command );
|
expcmd = expand_command ( command );
|
||||||
if ( ! expcmd )
|
if ( ! expcmd )
|
||||||
@ -291,23 +307,31 @@ int system ( const char *command ) {
|
|||||||
|
|
||||||
split_command ( expcmd, tokens );
|
split_command ( expcmd, tokens );
|
||||||
tokens[count] = NULL;
|
tokens[count] = NULL;
|
||||||
|
process = 1;
|
||||||
|
|
||||||
for ( argv = tokens ; ; argv += ( argc + 1 ) ) {
|
for ( argv = tokens ; ; argv += ( argc + 1 ) ) {
|
||||||
|
|
||||||
/* Find command terminator */
|
/* Find command terminator */
|
||||||
argc = command_terminator ( argv, &terminator );
|
argc = command_terminator ( argv, &process_next );
|
||||||
|
|
||||||
/* Execute command */
|
/* Execute command */
|
||||||
argv[argc] = NULL;
|
if ( process ) {
|
||||||
rc = execv ( argv[0], argv );
|
argv[argc] = NULL;
|
||||||
|
rc = execv ( argv[0], argv );
|
||||||
|
}
|
||||||
|
|
||||||
/* Check exit flag */
|
/* Stop processing, if applicable */
|
||||||
if ( shell_exit )
|
if ( shell_stopped ( SHELL_STOP_COMMAND ) )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Handle terminator */
|
/* Stop processing if we have reached the end
|
||||||
if ( terminator ( rc ) )
|
* of the command.
|
||||||
|
*/
|
||||||
|
if ( ! process_next )
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* Determine whether or not to process next command */
|
||||||
|
process = process_next ( rc );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +346,7 @@ int system ( const char *command ) {
|
|||||||
*
|
*
|
||||||
* @v argc Argument count
|
* @v argc Argument count
|
||||||
* @v argv Argument list
|
* @v argv Argument list
|
||||||
* @ret rc Exit code
|
* @ret rc Return status code
|
||||||
*/
|
*/
|
||||||
static int echo_exec ( int argc, char **argv ) {
|
static int echo_exec ( int argc, char **argv ) {
|
||||||
int i;
|
int i;
|
||||||
@ -373,8 +397,8 @@ static int exit_exec ( int argc, char **argv ) {
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set exit flag */
|
/* Stop shell processing */
|
||||||
shell_exit = 1;
|
shell_stop ( SHELL_STOP_COMMAND_SEQUENCE );
|
||||||
|
|
||||||
return exit_code;
|
return exit_code;
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,7 @@ int shell ( void ) {
|
|||||||
rc = system ( line );
|
rc = system ( line );
|
||||||
free ( line );
|
free ( line );
|
||||||
}
|
}
|
||||||
} while ( shell_exit == 0 );
|
} while ( ! shell_stopped ( SHELL_STOP_COMMAND_SEQUENCE ) );
|
||||||
shell_exit = 0;
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
|
|||||||
#include <ipxe/command.h>
|
#include <ipxe/command.h>
|
||||||
#include <ipxe/parseopt.h>
|
#include <ipxe/parseopt.h>
|
||||||
#include <ipxe/image.h>
|
#include <ipxe/image.h>
|
||||||
|
#include <ipxe/shell.h>
|
||||||
|
|
||||||
struct image_type script_image_type __image_type ( PROBE_NORMAL );
|
struct image_type script_image_type __image_type ( PROBE_NORMAL );
|
||||||
|
|
||||||
@ -106,13 +107,8 @@ static int process_script ( int ( * process_line ) ( const char *line ),
|
|||||||
*/
|
*/
|
||||||
static int terminate_on_exit_or_failure ( int rc ) {
|
static int terminate_on_exit_or_failure ( int rc ) {
|
||||||
|
|
||||||
/* Check and consume exit flag */
|
return ( shell_stopped ( SHELL_STOP_COMMAND_SEQUENCE ) ||
|
||||||
if ( shell_exit ) {
|
( rc != 0 ) );
|
||||||
shell_exit = 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( rc != 0 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -292,6 +288,9 @@ static int goto_exec ( int argc, char **argv ) {
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Terminate processing of current command */
|
||||||
|
shell_stop ( SHELL_STOP_COMMAND );
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,4 @@ struct command {
|
|||||||
|
|
||||||
#define __command __table_entry ( COMMANDS, 01 )
|
#define __command __table_entry ( COMMANDS, 01 )
|
||||||
|
|
||||||
extern int shell_exit;
|
|
||||||
|
|
||||||
#endif /* _IPXE_COMMAND_H */
|
#endif /* _IPXE_COMMAND_H */
|
||||||
|
@ -9,6 +9,28 @@
|
|||||||
|
|
||||||
FILE_LICENCE ( GPL2_OR_LATER );
|
FILE_LICENCE ( GPL2_OR_LATER );
|
||||||
|
|
||||||
|
/** Shell stop states */
|
||||||
|
enum shell_stop_state {
|
||||||
|
/** Continue processing */
|
||||||
|
SHELL_CONTINUE = 0,
|
||||||
|
/**
|
||||||
|
* Stop processing current command line
|
||||||
|
*
|
||||||
|
* This is the stop state entered by commands that change the flow
|
||||||
|
* of execution, such as "goto".
|
||||||
|
*/
|
||||||
|
SHELL_STOP_COMMAND = 1,
|
||||||
|
/**
|
||||||
|
* Stop processing commands
|
||||||
|
*
|
||||||
|
* This is the stop state entered by commands that terminate
|
||||||
|
* the flow of execution, such as "exit".
|
||||||
|
*/
|
||||||
|
SHELL_STOP_COMMAND_SEQUENCE = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void shell_stop ( int stop );
|
||||||
|
extern int shell_stopped ( int stop );
|
||||||
extern int shell ( void );
|
extern int shell ( void );
|
||||||
|
|
||||||
#endif /* _IPXE_SHELL_H */
|
#endif /* _IPXE_SHELL_H */
|
||||||
|
Reference in New Issue
Block a user