[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