]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[script] Implement "goto" in iPXE scripts
authorMichael Brown <mcb30@ipxe.org>
Mon, 22 Nov 2010 04:19:24 +0000 (04:19 +0000)
committerMichael Brown <mcb30@ipxe.org>
Mon, 22 Nov 2010 04:27:26 +0000 (04:27 +0000)
Allow script labels to be defined using the syntax

  :<labelname>

(nothing else allowed on the line, including whitespace).  Labels are
ignored during script execution, but can be used as the target of the
"goto" command.  For example:

  #!ipxe

  goto machine_${net0/ip} || goto machine_default

  # Linux kernel boot
  :machine_10.0.0.101
  :machine_10.0.0.102
  set filename http://my.boot.server/vmlinuz
  goto done

  # Default configuration
  :machine_default
  set filename pxelinux.0
  goto done

  # Boot selected configuration
  :done
  chain ${filename}

Originally-implemented-by: Shao Miller <shao.miller@yrdsb.edu.on.ca>
Originally-implemented-by: Stefan Hajnoczi <stefanha@gmail.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/image/script.c

index b65fa061f328b341b68b74794fbdf60cd2bd057f..ba098c2c1072f74ca5f33856ab3185239342ba37 100644 (file)
@@ -27,59 +27,148 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <string.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <ctype.h>
 #include <errno.h>
+#include <getopt.h>
+#include <ipxe/command.h>
+#include <ipxe/parseopt.h>
 #include <ipxe/image.h>
 
 struct image_type script_image_type __image_type ( PROBE_NORMAL );
 
+/** Currently running script
+ *
+ * This is a global in order to allow goto_exec() to update the
+ * offset.
+ */
+static struct image *script;
+
+/** Offset within current script
+ *
+ * This is a global in order to allow goto_exec() to update the
+ * offset.
+ */
+static size_t script_offset;
+
 /**
- * Execute script
+ * Process script lines
  *
- * @v image            Script
+ * @v process_line     Line processor
+ * @v terminate                Termination check
  * @ret rc             Return status code
  */
-static int script_exec ( struct image *image ) {
-       size_t offset = 0;
+static int process_script ( int ( * process_line ) ( const char *line ),
+                           int ( * terminate ) ( int rc ) ) {
        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 );
+       script_offset = 0;
 
-       while ( offset < image->len ) {
+       do {
        
                /* Find length of next line, excluding any terminating '\n' */
-               eol = memchr_user ( image->data, offset, '\n',
-                                   ( image->len - offset ) );
+               eol = memchr_user ( script->data, script_offset, '\n',
+                                   ( script->len - script_offset ) );
                if ( eol < 0 )
-                       eol = image->len;
-               len = ( eol - offset );
+                       eol = script->len;
+               len = ( eol - script_offset );
 
                /* Copy line, terminate with NUL, and execute command */
                {
                        char cmdbuf[ len + 1 ];
 
-                       copy_from_user ( cmdbuf, image->data, offset, len );
+                       copy_from_user ( cmdbuf, script->data,
+                                        script_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 */
+                       script_offset += ( len + 1 );
+
+                       /* Process line */
+                       rc = process_line ( cmdbuf );
+                       if ( terminate ( rc ) )
+                               return rc;
                }
-               
-               /* Move to next line */
-               offset += ( len + 1 );
+
+       } while ( script_offset < script->len );
+
+       return rc;
+}
+
+/**
+ * Terminate script processing if line processing failed
+ *
+ * @v rc               Line processing status
+ * @ret terminate      Terminate script processing
+ */
+static int terminate_on_failure ( int rc ) {
+       return ( rc != 0 );
+}
+
+/**
+ * Terminate script processing if line processing succeeded
+ *
+ * @v rc               Line processing status
+ * @ret terminate      Terminate script processing
+ */
+static int terminate_on_success ( int rc ) {
+       return ( rc == 0 );
+}
+
+/**
+ * Execute script line
+ *
+ * @v line             Line of script
+ * @ret rc             Return status code
+ */
+static int script_exec_line ( const char *line ) {
+       int rc;
+
+       /* Skip label lines */
+       if ( line[0] == ':' )
+               return 0;
+
+       /* Execute command */
+       if ( ( rc = system ( line ) ) != 0 ) {
+               printf ( "Aborting on \"%s\"\n", line );
+               return rc;
        }
 
-       rc = 0;
- done:
-       /* Re-register image and return */
+       return 0;
+}
+
+/**
+ * Execute script
+ *
+ * @v image            Script
+ * @ret rc             Return status code
+ */
+static int script_exec ( struct image *image ) {
+       struct image *saved_script;
+       size_t saved_offset;
+       int rc;
+
+       /* Temporarily de-register image, so that a "boot" command
+        * doesn't throw us into an execution loop.
+        */
+       unregister_image ( image );
+
+       /* Preserve state of any currently-running script */
+       saved_script = script;
+       saved_offset = script_offset;
+
+       /* Initialise state for this script */
+       script = image;
+
+       /* Process script */
+       rc = process_script ( script_exec_line, terminate_on_failure );
+
+       /* Restore saved state, re-register image, and return */
+       script_offset = saved_offset;
+       script = saved_script;
        register_image ( image );
        return rc;
 }
@@ -129,3 +218,78 @@ struct image_type script_image_type __image_type ( PROBE_NORMAL ) = {
        .load = script_load,
        .exec = script_exec,
 };
+
+/** "goto" options */
+struct goto_options {};
+
+/** "goto" option list */
+static struct option_descriptor goto_opts[] = {};
+
+/** "goto" command descriptor */
+static struct command_descriptor goto_cmd =
+       COMMAND_DESC ( struct goto_options, goto_opts, 1, 1,
+                      "<label>", "" );
+
+/**
+ * Current "goto" label
+ *
+ * Valid only during goto_exec().  Consider this part of a closure.
+ */
+static const char *goto_label;
+
+/**
+ * Check for presence of label
+ *
+ * @v line             Script line
+ * @ret rc             Return status code
+ */
+static int goto_find_label ( const char *line ) {
+
+       if ( line[0] != ':' )
+               return -ENOENT;
+       if ( strcmp ( goto_label, &line[1] ) != 0 )
+               return -ENOENT;
+       return 0;
+}
+
+/**
+ * "goto" command
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @ret rc             Return status code
+ */
+static int goto_exec ( int argc, char **argv ) {
+       struct goto_options opts;
+       size_t saved_offset;
+       int rc;
+
+       /* Parse options */
+       if ( ( rc = parse_options ( argc, argv, &goto_cmd, &opts ) ) != 0 )
+               return rc;
+
+       /* Sanity check */
+       if ( ! script ) {
+               printf ( "Not in a script\n" );
+               return -ENOTTY;
+       }
+
+       /* Parse label */
+       goto_label = argv[optind];
+
+       /* Find label */
+       saved_offset = script_offset;
+       if ( ( rc = process_script ( goto_find_label,
+                                    terminate_on_success ) ) != 0 ) {
+               script_offset = saved_offset;
+               return rc;
+       }
+
+       return 0;
+}
+
+/** "goto" command */
+struct command goto_command __command = {
+       .name = "goto",
+       .exec = goto_exec,
+};