X-Git-Url: http://git.ozlabs.org/?p=petitboot;a=blobdiff_plain;f=discover%2Fgrub2%2Fscript.c;h=0618297784499f5d60ac0e597e03ff8602ee05d7;hp=67b4b7801f8717d51df2cdbfc68edab97988f082;hb=37bff93c8b0a71432613f41f2319dc073ca64619;hpb=882f61e8eb44ec9f9becc32328667c65f364d066 diff --git a/discover/grub2/script.c b/discover/grub2/script.c index 67b4b78..0618297 100644 --- a/discover/grub2/script.c +++ b/discover/grub2/script.c @@ -1,7 +1,9 @@ #include #include +#include +#include #include #include @@ -9,18 +11,22 @@ #define to_stmt_simple(stmt) \ container_of(stmt, struct grub2_statement_simple, st) +#define to_stmt_block(stmt) \ + container_of(stmt, struct grub2_statement_block, st) #define to_stmt_if(stmt) \ container_of(stmt, struct grub2_statement_if, st) #define to_stmt_menuentry(stmt) \ container_of(stmt, struct grub2_statement_menuentry, st) #define to_stmt_function(stmt) \ container_of(stmt, struct grub2_statement_function, st) +#define to_stmt_for(stmt) \ + container_of(stmt, struct grub2_statement_for, st) #define to_stmt_conditional(stmt) \ container_of(stmt, struct grub2_statement_conditional, st) struct env_entry { - const char *name; - const char *value; + char *name; + char *value; struct list_item list; }; @@ -31,6 +37,8 @@ struct grub2_symtab_entry { struct list_item list; }; +static const char *default_prefix = "/boot/grub"; + static struct grub2_symtab_entry *script_lookup_function( struct grub2_script *script, const char *name) { @@ -69,14 +77,16 @@ void script_env_set(struct grub2_script *script, if (!entry) { entry = talloc(script, struct env_entry); - entry->name = name; + entry->name = talloc_strdup(entry, name); list_add(&script->environment, &entry->list); + } else { + talloc_free(entry->value); } - entry->value = value; + entry->value = talloc_strdup(entry, value); } -static bool expand_var(struct grub2_script *script, struct grub2_word *word) +static char *expand_var(struct grub2_script *script, struct grub2_word *word) { const char *val; @@ -84,9 +94,7 @@ static bool expand_var(struct grub2_script *script, struct grub2_word *word) if (!val) val = ""; - word->text = talloc_strdup(script, val); - - return true; + return talloc_strdup(script, val); } static bool is_delim(char c) @@ -94,125 +102,133 @@ static bool is_delim(char c) return c == ' ' || c == '\t'; } -/* For non-double-quoted variable expansions, we may need to split the - * variable's value into multiple argv items. - * - * This function sets the word->text to the initial set of non-delimiter chars - * in the expanded var value. We then skip any delimiter chars, and (if - * required), create the new argv item with the remaining text, and - * add it to the argv list, after top_word. - */ -static void process_split(struct grub2_script *script, - struct grub2_word *top_word, - struct grub2_word *word) +static bool option_is_default(struct grub2_script *script, + struct discover_boot_option *opt, const char *id) { - int i, len, delim_start = -1, delim_end = -1; - struct grub2_word *new_word; - char *remainder; + unsigned int default_idx; + const char *var; + char *end; - len = strlen(word->text); + var = script_env_get(script, "default"); + if (!var) + return false; - /* Scan our string for the start of a delim (delim_start), and the - * start of any new text (delim_end). */ - for (i = 0; i < len; i++) { - if (is_delim(word->text[i])) { - if (delim_start == -1) - delim_start = i; - } else if (delim_start != -1) { - delim_end = i; - break; - } + default_idx = strtoul(var, &end, 10); + if (end != var && *end == '\0') + return default_idx == script->n_options; + + /* if we don't have an explicit id for this option, fall back to + * the name */ + if (!id) + id = opt->option->name; + + return !strcmp(id, var); +} + +static void append_text_to_current_arg(struct grub2_argv *argv, + const char *text, int len) +{ + char *cur = argv->argv[argv->argc - 1]; + + if (cur) { + int curlen = strlen(cur); + cur = talloc_realloc(argv->argv, cur, char, len + curlen + 1); + memcpy(cur + curlen, text, len); + cur[len + curlen] = '\0'; + + } else { + cur = talloc_strndup(argv->argv, text, len); } - /* No delim? nothing to do. The process_expansions loop will - * append this word's text to the top word, if necessary - */ - if (delim_start == -1) + argv->argv[argv->argc-1] = cur; +} + +/* Add a word to the argv array. Depending on the word type, and presence of + * delimiter characters, we may add multiple items to the array. + */ +static void append_word_to_argv(struct grub2_script *script, + struct grub2_argv *argv, struct grub2_word *word) +{ + const char *text, *pos; + int i, len; + + /* If it's a variable, perform substitution */ + if (word->type == GRUB2_WORD_VAR) + text = expand_var(script, word); + else + text = word->text; + + len = strlen(text); + + /* If we have no text, we leave the current word as-is. The caller + * has allocated an empty string for the case where this is the + * first text token */ + if (!len) return; - /* Set this word's text value to the text before the delimiter. - * this will get appended to the top word - */ - word->text[delim_start] = '\0'; - - /* No trailing text? If there are no following word tokens, we're done. - * Otherwise, we need to start a new argv item with those tokens */ - if (delim_end == -1) { - if (!word->next) - return; - remainder = ""; - } else { - remainder = word->text + delim_end; + /* If we're not splitting, we just add the entire block to the + * current argv item */ + if (!word->split) { + append_text_to_current_arg(argv, text, len); + return; } - new_word = talloc(script, struct grub2_word); - new_word->type = GRUB2_WORD_TEXT; - /* if there's no trailing text, we know we don't need to re-split */ - new_word->split = delim_end != -1; - new_word->next = word->next; - new_word->last = NULL; - new_word->text = talloc_strdup(new_word, remainder); + /* Scan for delimiters. If we find a word-end boundary, add the word + * to the argv array, and start a new argv item */ + pos = !is_delim(text[0]) ? text : NULL; + for (i = 0; i < len; i++) { - /* stitch it into the argv list before this word */ - list_insert_after(&top_word->argv_list, - &new_word->argv_list); + /* first delimiter after a word: add the accumulated word to + * argv */ + if (pos && is_delim(text[i])) { + append_text_to_current_arg(argv, pos, + text + i - pos); + pos = NULL; + + /* first non-delimeter after a delimiter: record the starting + * position, and create another argv item */ + } else if (!pos && !is_delim(text[i])) { + pos = text + i; + argv->argc++; + argv->argv = talloc_realloc(argv, argv->argv, char *, + argv->argc); + argv->argv[argv->argc - 1] = NULL; + } + } - /* terminate this word */ - word->next = NULL; + /* add remaining word characters */ + if (pos) + append_text_to_current_arg(argv, pos, text + len - pos); } -/* iterate through the words in an argv_list, looking for GRUB2_WORD_VAR +/* Transform an argv word-token list (returned from the parser) into an + * expanded argv array (as used by the script execution code). We do this by + * iterating through the words in an argv_list, looking for GRUB2_WORD_VAR * expansions. - * - * Once that's done, we may (if split == true) have to split the word to create - * new argv items */ static void process_expansions(struct grub2_script *script, struct grub2_argv *argv) { struct grub2_word *top_word, *word; - int i; argv->argc = 0; + argv->argv = NULL; list_for_each_entry(&argv->words, top_word, argv_list) { argv->argc++; - - /* expand vars and squash the list of words into the head - * of the argv word list */ - for (word = top_word; word; word = word->next) { - - /* if it's a variable, perform the substitution */ - if (word->type == GRUB2_WORD_VAR) { - expand_var(script, word); - word->type = GRUB2_WORD_TEXT; - } - - /* split; this will potentially insert argv - * entries after top_word. */ - if (word->split) - process_split(script, top_word, word); - - /* accumulate word text into the top word, so - * we end up with a shallow tree of argv data */ - /* todo: don't do this in process_split */ - if (word != top_word) { - top_word->text = talloc_asprintf_append( - top_word->text, - "%s", word->text); - } - - - } - top_word->next = NULL; + argv->argv = talloc_realloc(argv, argv->argv, char *, + argv->argc); + /* because we've parsed a separate word here, we know that + * we need at least an empty string */ + argv->argv[argv->argc - 1] = talloc_strdup(argv->argv, ""); + + for (word = top_word; word; word = word->next) + append_word_to_argv(script, argv, word); } - /* convert the list to an argv array, to pass to the function */ - argv->argv = talloc_array(script, char *, argv->argc); - i = 0; - - list_for_each_entry(&argv->words, word, argv_list) - argv->argv[i++] = word->text; + /* we may have allocated an extra argv element but not populated it */ + if (!argv->argv[argv->argc - 1]) + argv->argc--; } static int statements_execute(struct grub2_script *script, @@ -233,6 +249,7 @@ int statement_simple_execute(struct grub2_script *script, { struct grub2_statement_simple *st = to_stmt_simple(statement); struct grub2_symtab_entry *entry; + char *pos; int rc; if (!st->argv) @@ -243,10 +260,21 @@ int statement_simple_execute(struct grub2_script *script, if (!st->argv->argc) return 0; + /* is this a var=value assignment? */ + pos = strchr(st->argv->argv[0], '='); + if (pos) { + char *name, *value; + name = st->argv->argv[0]; + name = talloc_strndup(st, name, pos - name); + value = pos + 1; + script_env_set(script, name, value); + return 0; + } + entry = script_lookup_function(script, st->argv->argv[0]); if (!entry) { - fprintf(stderr, "undefined function '%s'\n", st->argv->argv[0]); - return 0; + pb_log("grub2: undefined function '%s'\n", st->argv->argv[0]); + return 1; } rc = entry->fn(script, entry->data, st->argv->argc, st->argv->argv); @@ -254,6 +282,13 @@ int statement_simple_execute(struct grub2_script *script, return rc; } +int statement_block_execute(struct grub2_script *script, + struct grub2_statement *statement) +{ + struct grub2_statement_block *st = to_stmt_block(statement); + return statements_execute(script, st->statements); +} + /* returns 0 if the statement was executed, 1 otherwise */ static int statement_conditional_execute(struct grub2_script *script, struct grub2_statement *statement, bool *executed) @@ -275,7 +310,7 @@ int statement_if_execute(struct grub2_script *script, struct grub2_statement_if *st = to_stmt_if(statement); struct grub2_statement *conditional; bool executed; - int rc; + int rc = 0; list_for_each_entry(&st->conditionals->list, conditional, list) { rc = statement_conditional_execute(script, @@ -295,24 +330,42 @@ int statement_menuentry_execute(struct grub2_script *script, { struct grub2_statement_menuentry *st = to_stmt_menuentry(statement); struct discover_boot_option *opt; + const char *id = NULL; + int i; process_expansions(script, st->argv); opt = discover_boot_option_create(script->ctx, script->ctx->device); - if (st->argv->argc > 0) { + + /* XXX: --options=values need to be parsed properly; this is a simple + * implementation to get --id= working. + */ + for (i = 1; i < st->argv->argc; ++i) { + if (strncmp("--id=", st->argv->argv[i], 5) == 0) { + id = st->argv->argv[i] + 5; + break; + } + } + if (st->argv->argc > 0) opt->option->name = talloc_strdup(opt, st->argv->argv[0]); - } else { + else opt->option->name = talloc_strdup(opt, "(unknown)"); - } + opt->option->id = talloc_asprintf(opt->option, "%s#%s", script->ctx->device->device->id, - opt->option->name); + id ? id : opt->option->name); script->opt = opt; statements_execute(script, st->statements); + if (!opt->boot_image) + return -1; + + opt->option->is_default = option_is_default(script, opt, id); + discover_context_add_boot_option(script->ctx, opt); + script->n_options++; script->opt = NULL; return 0; @@ -341,25 +394,60 @@ int statement_function_execute(struct grub2_script *script, const char *name; if (st->name->type == GRUB2_WORD_VAR) - expand_var(script, st->name); + name = expand_var(script, st->name); + else + name = st->name->text; - name = st->name->text; script_register_function(script, name, function_invoke, st); return 0; } +int statement_for_execute(struct grub2_script *script, + struct grub2_statement *statement) +{ + struct grub2_statement_for *st = to_stmt_for(statement); + const char *varname; + int i, rc = 0; + + if (st->var->type == GRUB2_WORD_VAR) + expand_var(script, st->var); + varname = st->var->text; + + process_expansions(script, st->list); + + for (i = 0; i < st->list->argc; ++i) { + script_env_set(script, varname, st->list->argv[i]); + rc = statements_execute(script, st->body); + } + + return rc; +} + static void init_env(struct grub2_script *script) { struct env_entry *env; + char *prefix, *sep; list_init(&script->environment); + /* use location of the parsed config file to determine the prefix */ env = talloc(script, struct env_entry); - env->name = talloc_strdup(env, "prefix"); - env->value = talloc_strdup(env, "/"); - list_add(&script->environment, &env->list); + prefix = NULL; + if (script->filename) { + sep = strrchr(script->filename, '/'); + if (sep) + prefix = talloc_strndup(env, script->filename, + sep - script->filename); + } + + script_env_set(script, "prefix", prefix ? : default_prefix); + if (prefix) + talloc_free(prefix); + + /* establish feature settings */ + script_env_set(script, "feature_menuentry_id", "y"); } void script_register_function(struct grub2_script *script, @@ -378,6 +466,7 @@ void script_register_function(struct grub2_script *script, void script_execute(struct grub2_script *script) { + init_env(script); statements_execute(script, script->statements); } @@ -386,11 +475,9 @@ struct grub2_script *create_script(struct grub2_parser *parser, { struct grub2_script *script; - script = talloc(parser, struct grub2_script); + script = talloc_zero(parser, struct grub2_script); - init_env(script); script->ctx = ctx; - script->opt = NULL; list_init(&script->symtab); register_builtins(script);