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

Reply via email to