iPXE
script.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /**
00027  * @file
00028  *
00029  * iPXE scripts
00030  *
00031  */
00032 
00033 #include <string.h>
00034 #include <stdlib.h>
00035 #include <stdio.h>
00036 #include <ctype.h>
00037 #include <errno.h>
00038 #include <getopt.h>
00039 #include <ipxe/command.h>
00040 #include <ipxe/parseopt.h>
00041 #include <ipxe/image.h>
00042 #include <ipxe/shell.h>
00043 #include <usr/prompt.h>
00044 #include <ipxe/script.h>
00045 
00046 /** Offset within current script
00047  *
00048  * This is a global in order to allow goto_exec() to update the
00049  * offset.
00050  */
00051 static size_t script_offset;
00052 
00053 /**
00054  * Process script lines
00055  *
00056  * @v image             Script
00057  * @v process_line      Line processor
00058  * @v terminate         Termination check
00059  * @ret rc              Return status code
00060  */
00061 static int process_script ( struct image *image,
00062                             int ( * process_line ) ( struct image *image,
00063                                                      size_t offset,
00064                                                      const char *label,
00065                                                      const char *command ),
00066                             int ( * terminate ) ( int rc ) ) {
00067         size_t len = 0;
00068         char *line = NULL;
00069         size_t line_offset;
00070         char *label;
00071         char *command;
00072         off_t eol;
00073         size_t frag_len;
00074         char *tmp;
00075         int rc;
00076 
00077         /* Initialise script and line offsets */
00078         script_offset = 0;
00079         line_offset = 0;
00080 
00081         do {
00082 
00083                 /* Find length of next line, excluding any terminating '\n' */
00084                 eol = memchr_user ( image->data, script_offset, '\n',
00085                                     ( image->len - script_offset ) );
00086                 if ( eol < 0 )
00087                         eol = image->len;
00088                 frag_len = ( eol - script_offset );
00089 
00090                 /* Allocate buffer for line */
00091                 tmp = realloc ( line, ( len + frag_len + 1 /* NUL */ ) );
00092                 if ( ! tmp ) {
00093                         rc = -ENOMEM;
00094                         goto err_alloc;
00095                 }
00096                 line = tmp;
00097 
00098                 /* Copy line */
00099                 copy_from_user ( ( line + len ), image->data, script_offset,
00100                                  frag_len );
00101                 len += frag_len;
00102 
00103                 /* Move to next line in script */
00104                 script_offset += ( frag_len + 1 );
00105 
00106                 /* Strip trailing CR, if present */
00107                 if ( len && ( line[ len - 1 ] == '\r' ) )
00108                         len--;
00109 
00110                 /* Handle backslash continuations */
00111                 if ( len && ( line[ len - 1 ] == '\\' ) ) {
00112                         len--;
00113                         rc = -EINVAL;
00114                         continue;
00115                 }
00116 
00117                 /* Terminate line */
00118                 line[len] = '\0';
00119 
00120                 /* Split line into (optional) label and command */
00121                 command = line;
00122                 while ( isspace ( *command ) )
00123                         command++;
00124                 if ( *command == ':' ) {
00125                         label = ++command;
00126                         while ( *command && ! isspace ( *command ) )
00127                                 command++;
00128                         if ( *command )
00129                                 *(command++) = '\0';
00130                 } else {
00131                         label = NULL;
00132                 }
00133 
00134                 /* Process line */
00135                 rc = process_line ( image, line_offset, label, command );
00136                 if ( terminate ( rc ) )
00137                         goto err_process;
00138 
00139                 /* Free line */
00140                 free ( line );
00141                 line = NULL;
00142                 len = 0;
00143 
00144                 /* Update line offset */
00145                 line_offset = script_offset;
00146 
00147         } while ( script_offset < image->len );
00148 
00149  err_process:
00150  err_alloc:
00151         free ( line );
00152         return rc;
00153 }
00154 
00155 /**
00156  * Terminate script processing on shell exit or command failure
00157  *
00158  * @v rc                Line processing status
00159  * @ret terminate       Terminate script processing
00160  */
00161 static int terminate_on_exit_or_failure ( int rc ) {
00162 
00163         return ( shell_stopped ( SHELL_STOP_COMMAND_SEQUENCE ) ||
00164                  ( rc != 0 ) );
00165 }
00166 
00167 /**
00168  * Execute script line
00169  *
00170  * @v image             Script
00171  * @v offset            Offset within script
00172  * @v label             Label, or NULL
00173  * @v command           Command
00174  * @ret rc              Return status code
00175  */
00176 static int script_exec_line ( struct image *image, size_t offset,
00177                               const char *label __unused,
00178                               const char *command ) {
00179         int rc;
00180 
00181         DBGC ( image, "[%04zx] $ %s\n", offset, command );
00182 
00183         /* Execute command */
00184         if ( ( rc = system ( command ) ) != 0 )
00185                 return rc;
00186 
00187         return 0;
00188 }
00189 
00190 /**
00191  * Execute script
00192  *
00193  * @v image             Script
00194  * @ret rc              Return status code
00195  */
00196 static int script_exec ( struct image *image ) {
00197         size_t saved_offset;
00198         int rc;
00199 
00200         /* Temporarily de-register image, so that a "boot" command
00201          * doesn't throw us into an execution loop.
00202          */
00203         unregister_image ( image );
00204 
00205         /* Preserve state of any currently-running script */
00206         saved_offset = script_offset;
00207 
00208         /* Process script */
00209         rc = process_script ( image, script_exec_line,
00210                               terminate_on_exit_or_failure );
00211 
00212         /* Restore saved state */
00213         script_offset = saved_offset;
00214 
00215         /* Re-register image (unless we have been replaced) */
00216         if ( ! image->replacement )
00217                 register_image ( image );
00218 
00219         return rc;
00220 }
00221 
00222 /**
00223  * Probe script image
00224  *
00225  * @v image             Script
00226  * @ret rc              Return status code
00227  */
00228 static int script_probe ( struct image *image ) {
00229         static const char ipxe_magic[] = "#!ipxe";
00230         static const char gpxe_magic[] = "#!gpxe";
00231         linker_assert ( sizeof ( ipxe_magic ) == sizeof ( gpxe_magic ),
00232                         magic_size_mismatch );
00233         char test[ sizeof ( ipxe_magic ) - 1 /* NUL */
00234                    + 1 /* terminating space */];
00235 
00236         /* Sanity check */
00237         if ( image->len < sizeof ( test ) ) {
00238                 DBGC ( image, "Too short to be a script\n" );
00239                 return -ENOEXEC;
00240         }
00241 
00242         /* Check for magic signature */
00243         copy_from_user ( test, image->data, 0, sizeof ( test ) );
00244         if ( ! ( ( ( memcmp ( test, ipxe_magic, sizeof ( test ) - 1 ) == 0 ) ||
00245                    ( memcmp ( test, gpxe_magic, sizeof ( test ) - 1 ) == 0 )) &&
00246                  isspace ( test[ sizeof ( test ) - 1 ] ) ) ) {
00247                 DBGC ( image, "Invalid magic signature\n" );
00248                 return -ENOEXEC;
00249         }
00250 
00251         return 0;
00252 }
00253 
00254 /** Script image type */
00255 struct image_type script_image_type __image_type ( PROBE_NORMAL ) = {
00256         .name = "script",
00257         .probe = script_probe,
00258         .exec = script_exec,
00259 };
00260 
00261 /** "goto" options */
00262 struct goto_options {};
00263 
00264 /** "goto" option list */
00265 static struct option_descriptor goto_opts[] = {};
00266 
00267 /** "goto" command descriptor */
00268 static struct command_descriptor goto_cmd =
00269         COMMAND_DESC ( struct goto_options, goto_opts, 1, 1, "<label>" );
00270 
00271 /**
00272  * Current "goto" label
00273  *
00274  * Valid only during goto_exec().  Consider this part of a closure.
00275  */
00276 static const char *goto_label;
00277 
00278 /**
00279  * Check for presence of label
00280  *
00281  * @v image             Script
00282  * @v offset            Offset within script
00283  * @v label             Label
00284  * @v command           Command
00285  * @ret rc              Return status code
00286  */
00287 static int goto_find_label ( struct image *image, size_t offset,
00288                              const char *label, const char *command __unused ) {
00289 
00290         /* Check label exists */
00291         if ( ! label )
00292                 return -ENOENT;
00293 
00294         /* Check label matches */
00295         if ( strcmp ( goto_label, label ) != 0 )
00296                 return -ENOENT;
00297 
00298         /* Update script offset */
00299         script_offset = offset;
00300         DBGC ( image, "[%04zx] Gone to :%s\n", offset, label );
00301 
00302         return 0;
00303 }
00304 
00305 /**
00306  * Terminate script processing when label is found
00307  *
00308  * @v rc                Line processing status
00309  * @ret terminate       Terminate script processing
00310  */
00311 static int terminate_on_label_found ( int rc ) {
00312         return ( rc == 0 );
00313 }
00314 
00315 /**
00316  * "goto" command
00317  *
00318  * @v argc              Argument count
00319  * @v argv              Argument list
00320  * @ret rc              Return status code
00321  */
00322 static int goto_exec ( int argc, char **argv ) {
00323         struct goto_options opts;
00324         size_t saved_offset;
00325         int rc;
00326 
00327         /* Parse options */
00328         if ( ( rc = parse_options ( argc, argv, &goto_cmd, &opts ) ) != 0 )
00329                 return rc;
00330 
00331         /* Sanity check */
00332         if ( ! current_image ) {
00333                 rc = -ENOTTY;
00334                 printf ( "Not in a script: %s\n", strerror ( rc ) );
00335                 return rc;
00336         }
00337 
00338         /* Parse label */
00339         goto_label = argv[optind];
00340 
00341         /* Find label */
00342         saved_offset = script_offset;
00343         if ( ( rc = process_script ( current_image, goto_find_label,
00344                                      terminate_on_label_found ) ) != 0 ) {
00345                 script_offset = saved_offset;
00346                 DBGC ( current_image, "[%04zx] No such label :%s\n",
00347                        script_offset, goto_label );
00348                 return rc;
00349         }
00350 
00351         /* Terminate processing of current command */
00352         shell_stop ( SHELL_STOP_COMMAND );
00353 
00354         return 0;
00355 }
00356 
00357 /** "goto" command */
00358 struct command goto_command __command = {
00359         .name = "goto",
00360         .exec = goto_exec,
00361 };
00362 
00363 /** "prompt" options */
00364 struct prompt_options {
00365         /** Key to wait for */
00366         unsigned int key;
00367         /** Timeout */
00368         unsigned long timeout;
00369 };
00370 
00371 /** "prompt" option list */
00372 static struct option_descriptor prompt_opts[] = {
00373         OPTION_DESC ( "key", 'k', required_argument,
00374                       struct prompt_options, key, parse_key ),
00375         OPTION_DESC ( "timeout", 't', required_argument,
00376                       struct prompt_options, timeout, parse_timeout ),
00377 };
00378 
00379 /** "prompt" command descriptor */
00380 static struct command_descriptor prompt_cmd =
00381         COMMAND_DESC ( struct prompt_options, prompt_opts, 0, MAX_ARGUMENTS,
00382                        "[<text>]" );
00383 
00384 /**
00385  * "prompt" command
00386  *
00387  * @v argc              Argument count
00388  * @v argv              Argument list
00389  * @ret rc              Return status code
00390  */
00391 static int prompt_exec ( int argc, char **argv ) {
00392         struct prompt_options opts;
00393         char *text;
00394         int rc;
00395 
00396         /* Parse options */
00397         if ( ( rc = parse_options ( argc, argv, &prompt_cmd, &opts ) ) != 0 )
00398                 goto err_parse;
00399 
00400         /* Parse prompt text */
00401         text = concat_args ( &argv[optind] );
00402         if ( ! text ) {
00403                 rc = -ENOMEM;
00404                 goto err_concat;
00405         }
00406 
00407         /* Display prompt and wait for key */
00408         if ( ( rc = prompt ( text, opts.timeout, opts.key ) ) != 0 )
00409                 goto err_prompt;
00410 
00411         /* Free prompt text */
00412         free ( text );
00413 
00414         return 0;
00415 
00416  err_prompt:
00417         free ( text );
00418  err_concat:
00419  err_parse:
00420         return rc;
00421 }
00422 
00423 /** "prompt" command */
00424 struct command prompt_command __command = {
00425         .name = "prompt",
00426         .exec = prompt_exec,
00427 };