This patch adds the following control flow commands: if <condition> <true-cmd>
Execute <true-cmd> if <condition> is non-zero : <label> Define a label goto <label> Jump to label in a script. Not supported from the shell. These commands allow loops and conditionals to be expressed in scripts. Signed-off-by: Stefan Hajnoczi <[email protected]> --- src/image/script.c | 245 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 223 insertions(+), 22 deletions(-) diff --git a/src/image/script.c b/src/image/script.c index 0835ecb..c1792b5 100644 --- a/src/image/script.c +++ b/src/image/script.c @@ -27,31 +27,52 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <string.h> #include <stdlib.h> +#include <stdio.h> #include <ctype.h> #include <errno.h> +#include <unistd.h> #include <gpxe/image.h> +#include <gpxe/command.h> struct image_type script_image_type __image_type ( PROBE_NORMAL ); /** - * Execute script + * An executing script + */ +struct script_state { + /** Stack of executing scripts */ + struct list_head list; + /** Script image */ + struct image *image; + /** Offset to next line in script */ + size_t next_offset; +}; + +/** Stack of executing scripts */ +static LIST_HEAD ( script_states ); + +/** + * Pass script lines to a helper function * - * @v image Script + * @v image Script image + * @v process_line Helper function to process lines + * @v priv Private data for helper function * @ret rc Return status code + * + * Each script line is passed to the helper function, which may abort iteration + * by returning non-zero. The helper function can choose the next line by + * modifying its offset parameter. */ -static int script_exec ( struct image *image ) { +static int process_script ( struct image *image, + int ( * process_line ) ( const char *cmd, size_t *offset, + void *priv ), + void *priv ) { size_t offset = 0; off_t eol; size_t len; - int rc; - - /* Temporarily de-register image, so that a "boot" command - * doesn't throw us into an execution loop. - */ - unregister_image ( image ); + int rc = 0; - while ( offset < image->len ) { - + while ( ! rc && offset < image->len ) { /* Find length of next line, excluding any terminating '\n' */ eol = memchr_user ( image->data, offset, '\n', ( image->len - offset ) ); @@ -65,20 +86,69 @@ static int script_exec ( struct image *image ) { copy_from_user ( cmdbuf, image->data, offset, len ); cmdbuf[len] = '\0'; - DBG ( "$ %s\n", cmdbuf ); - if ( ( rc = system ( cmdbuf ) ) != 0 ) { - DBG ( "Command \"%s\" failed: %s\n", - cmdbuf, strerror ( rc ) ); - goto done; - } + + /* Move to next line */ + offset += len + 1; + + rc = process_line ( cmdbuf, &offset, priv ); } - - /* Move to next line */ - offset += ( len + 1 ); } + return rc; +} + +/** + * Execute a script line + * + * @v cmd Script line + * @v offset Offset to next line + * @v priv Private data + * @ret rc Return status code + * + * This helper function is passed to process_script(). + */ +static int exec_line ( const char *cmd, size_t *offset, void *priv ) { + struct script_state *state = priv; + int rc; + + /* Mark next line */ + state->next_offset = *offset; + + DBG ( "$ %s\n", cmd ); + if ( ( rc = system ( cmd ) ) != 0 ) { + DBG ( "Command \"%s\" failed: %s\n", + cmd, strerror ( rc ) ); + } + + /* Move to next line */ + *offset = state->next_offset; + + return rc; +} + +/** + * Execute script + * + * @v image Script + * @ret rc Return status code + */ +static int script_exec ( struct image *image ) { + struct script_state state; + int rc; + + /* Temporarily de-register image, so that a "boot" command + * doesn't throw us into an execution loop. + */ + unregister_image ( image ); + + /* Push executing script onto stack */ + state.image = image; + list_add ( &state.list, &script_states ); + + rc = process_script ( image, exec_line, &state ); + + /* Pop terminated script from stack */ + list_del ( &state.list ); - rc = 0; - done: /* Re-register image and return */ register_image ( image ); return rc; @@ -124,3 +194,134 @@ struct image_type script_image_type __image_type ( PROBE_NORMAL ) = { .load = script_load, .exec = script_exec, }; + +/** + * A label search + */ +struct find_label { + /** The label being searched for */ + const char *label; + /** Offset to the next script line */ + size_t offset; +}; + +/** + * Check a line for a label + * + * @v cmd Script line + * @v offset Offset to next line + * @v priv Private data + * @ret rc Return status code + * + * This helper function is passed to process_script(). + */ +static int find_label_line ( const char *cmd, size_t *offset, void *priv ) { + struct find_label *fl = priv; + + /* Stash away offset to next line */ + fl->offset = *offset; + + /* Check for label, whitespaces are not skipped */ + return ( cmd[0] == ':' && cmd[1] == ' ' && + strcmp ( &cmd[2], fl->label ) == 0 ); +} + +/** + * Find offset to a label in a script + * + * @v image Script image + * @v label Label to search for + * @ret offset Offset to label in script, or >= image->len on failure + */ +static size_t find_label_offset ( struct image *image, const char *label ) { + struct find_label fl = { + .label = label, + }; + + /* Search the script for the label definition. No need to check the + * return value because on failure, fl.offset is beyond the end of + * script. */ + process_script ( image, find_label_line, &fl ); + return fl.offset; +} + +/** + * The "if" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Exit code + */ +static int if_exec ( int argc, char **argv ) { + if ( argc < 3 ) { + printf ( "Usage:\n" + " if <condition> <true-cmd>\n" + "\n" + "Execute <true-cmd> if <condition> is non-zero\n" ); + return 1; + } + if ( strtoul ( argv[1], NULL, 0 ) ) + return execv ( argv[2], &argv[2] ); + return 0; +} + +/** + * The ":" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Exit code + */ +static int label_exec ( int argc __unused, char **argv __unused ) { + return 0; +} + +/** + * The "goto" command + * + * @v argc Argument count + * @v argv Argument list + * @ret rc Exit code + */ +static int goto_exec ( int argc, char **argv ) { + struct script_state *state = NULL; + size_t label_offset; + + if ( argc != 2 ) + goto usage; + + list_for_each_entry ( state, &script_states, list ) { + label_offset = find_label_offset ( state->image, argv[1] ); + if ( label_offset >= state->image->len ) { + printf ( "Undefined label: %s\n", argv[1] ); + return 1; + } + state->next_offset = label_offset; + return 0; + } + /* No script is executing, must be called from the shell */ + + usage: + printf ( "Usage:\n" + " %s <label>\n" + "\n" + "Jump to label in a script. Not supported from the shell.\n", + argv[0] ); + return 1; +} + +/** Script commands */ +struct command script_commands[] __command = { + { + .name = "if", + .exec = if_exec, + }, + { + .name = ":", + .exec = label_exec, + }, + { + .name = "goto", + .exec = goto_exec, + }, +}; -- 1.7.0 _______________________________________________ gPXE-devel mailing list [email protected] http://etherboot.org/mailman/listinfo/gpxe-devel
