[gPXE-devel] [PATCH] [script] Control flow using if and goto
Stefan Hajnoczi
stefanha at gmail.com
Sun Mar 14 05:58:24 EDT 2010
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 <stefanha at gmail.com>
---
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
More information about the gPXE-devel
mailing list