7 #include <types/types.h>
8 #include <talloc/talloc.h>
12 #define to_stmt_simple(stmt) \
13 container_of(stmt, struct grub2_statement_simple, st)
14 #define to_stmt_block(stmt) \
15 container_of(stmt, struct grub2_statement_block, st)
16 #define to_stmt_if(stmt) \
17 container_of(stmt, struct grub2_statement_if, st)
18 #define to_stmt_menuentry(stmt) \
19 container_of(stmt, struct grub2_statement_menuentry, st)
20 #define to_stmt_function(stmt) \
21 container_of(stmt, struct grub2_statement_function, st)
22 #define to_stmt_for(stmt) \
23 container_of(stmt, struct grub2_statement_for, st)
24 #define to_stmt_conditional(stmt) \
25 container_of(stmt, struct grub2_statement_conditional, st)
30 struct list_item list;
33 struct grub2_symtab_entry {
37 struct list_item list;
40 static const char *default_prefix = "/boot/grub";
42 static struct grub2_symtab_entry *script_lookup_function(
43 struct grub2_script *script, const char *name)
45 struct grub2_symtab_entry *entry;
47 list_for_each_entry(&script->symtab, entry, list) {
48 if (!strcmp(entry->name, name))
55 const char *script_env_get(struct grub2_script *script, const char *name)
57 struct env_entry *entry;
59 list_for_each_entry(&script->environment, entry, list)
60 if (!strcmp(entry->name, name))
66 void script_env_set(struct grub2_script *script,
67 const char *name, const char *value)
69 struct env_entry *e, *entry = NULL;
71 list_for_each_entry(&script->environment, e, list) {
72 if (!strcmp(e->name, name)) {
79 entry = talloc(script, struct env_entry);
80 entry->name = talloc_strdup(entry, name);
81 list_add(&script->environment, &entry->list);
83 talloc_free(entry->value);
86 entry->value = talloc_strdup(entry, value);
89 static char *expand_var(struct grub2_script *script, struct grub2_word *word)
93 val = script_env_get(script, word->name);
97 return talloc_strdup(script, val);
100 static bool is_delim(char c)
102 return c == ' ' || c == '\t';
105 static bool option_is_default(struct grub2_script *script,
106 struct discover_boot_option *opt, const char *id)
108 unsigned int default_idx;
112 var = script_env_get(script, "default");
116 default_idx = strtoul(var, &end, 10);
117 if (end != var && *end == '\0')
118 return default_idx == script->n_options;
120 if (id && !strcmp(id, var))
123 return !strcmp(opt->option->name, var);
126 static void append_text_to_current_arg(struct grub2_argv *argv,
127 const char *text, int len)
129 char *cur = argv->argv[argv->argc - 1];
132 int curlen = strlen(cur);
133 cur = talloc_realloc(argv->argv, cur, char, len + curlen + 1);
134 memcpy(cur + curlen, text, len);
135 cur[len + curlen] = '\0';
138 cur = talloc_strndup(argv->argv, text, len);
141 argv->argv[argv->argc-1] = cur;
144 /* Add a word to the argv array. Depending on the word type, and presence of
145 * delimiter characters, we may add multiple items to the array.
147 static void append_word_to_argv(struct grub2_script *script,
148 struct grub2_argv *argv, struct grub2_word *word)
150 const char *text, *pos;
153 /* If it's a variable, perform substitution */
154 if (word->type == GRUB2_WORD_VAR)
155 text = expand_var(script, word);
161 /* If we have no text, we leave the current word as-is. The caller
162 * has allocated an empty string for the case where this is the
163 * first text token */
167 /* If we're not splitting, we just add the entire block to the
168 * current argv item */
170 append_text_to_current_arg(argv, text, len);
174 /* Scan for delimiters. If we find a word-end boundary, add the word
175 * to the argv array, and start a new argv item */
176 pos = !is_delim(text[0]) ? text : NULL;
177 for (i = 0; i < len; i++) {
179 /* first delimiter after a word: add the accumulated word to
181 if (pos && is_delim(text[i])) {
182 append_text_to_current_arg(argv, pos,
186 /* first non-delimeter after a delimiter: record the starting
187 * position, and create another argv item */
188 } else if (!pos && !is_delim(text[i])) {
191 argv->argv = talloc_realloc(argv, argv->argv, char *,
193 argv->argv[argv->argc - 1] = NULL;
197 /* add remaining word characters */
199 append_text_to_current_arg(argv, pos, text + len - pos);
202 /* Transform an argv word-token list (returned from the parser) into an
203 * expanded argv array (as used by the script execution code). We do this by
204 * iterating through the words in an argv_list, looking for GRUB2_WORD_VAR
207 static void process_expansions(struct grub2_script *script,
208 struct grub2_argv *argv)
210 struct grub2_word *top_word, *word;
215 list_for_each_entry(&argv->words, top_word, argv_list) {
217 argv->argv = talloc_realloc(argv, argv->argv, char *,
219 /* because we've parsed a separate word here, we know that
220 * we need at least an empty string */
221 argv->argv[argv->argc - 1] = talloc_strdup(argv->argv, "");
223 for (word = top_word; word; word = word->next)
224 append_word_to_argv(script, argv, word);
227 /* we may have allocated an extra argv element but not populated it */
228 if (argv->argv && !argv->argv[argv->argc - 1])
232 static int statements_execute(struct grub2_script *script,
233 struct grub2_statements *stmts)
235 struct grub2_statement *stmt;
238 list_for_each_entry(&stmts->list, stmt, list) {
240 rc = stmt->exec(script, stmt);
245 int statement_simple_execute(struct grub2_script *script,
246 struct grub2_statement *statement)
248 struct grub2_statement_simple *st = to_stmt_simple(statement);
249 struct grub2_symtab_entry *entry;
256 process_expansions(script, st->argv);
261 /* is this a var=value assignment? */
262 pos = strchr(st->argv->argv[0], '=');
265 name = st->argv->argv[0];
266 name = talloc_strndup(st, name, pos - name);
268 script_env_set(script, name, value);
272 entry = script_lookup_function(script, st->argv->argv[0]);
274 pb_log("grub2: undefined function '%s'\n", st->argv->argv[0]);
278 rc = entry->fn(script, entry->data, st->argv->argc, st->argv->argv);
283 int statement_block_execute(struct grub2_script *script,
284 struct grub2_statement *statement)
286 struct grub2_statement_block *st = to_stmt_block(statement);
287 return statements_execute(script, st->statements);
290 /* returns 0 if the statement was executed, 1 otherwise */
291 static int statement_conditional_execute(struct grub2_script *script,
292 struct grub2_statement *statement, bool *executed)
294 struct grub2_statement_conditional *st = to_stmt_conditional(statement);
297 rc = st->condition->exec(script, st->condition);
300 rc = statements_execute(script, st->statements);
305 int statement_if_execute(struct grub2_script *script,
306 struct grub2_statement *statement)
308 struct grub2_statement_if *st = to_stmt_if(statement);
309 struct grub2_statement *conditional;
310 bool executed = false;
313 list_for_each_entry(&st->conditionals->list, conditional, list) {
314 rc = statement_conditional_execute(script,
315 conditional, &executed);
320 if (!executed && st->else_case)
321 rc = statements_execute(script, st->else_case);
326 int statement_menuentry_execute(struct grub2_script *script,
327 struct grub2_statement *statement)
329 struct grub2_statement_menuentry *st = to_stmt_menuentry(statement);
330 struct discover_boot_option *opt;
331 const char *id = NULL;
334 process_expansions(script, st->argv);
336 opt = discover_boot_option_create(script->ctx, script->ctx->device);
338 /* XXX: --options=values need to be parsed properly; this is a simple
339 * implementation to get --id= working.
341 for (i = 1; i < st->argv->argc; ++i) {
342 if (strncmp("--id", st->argv->argv[i], strlen("--id")) == 0) {
343 if (strlen(st->argv->argv[i]) > strlen("--id=")) {
344 id = st->argv->argv[i] + strlen("--id=");
348 if (i + 1 < st->argv->argc) {
349 id = st->argv->argv[i + 1];
354 if (st->argv->argc > 0)
355 opt->option->name = talloc_strdup(opt, st->argv->argv[0]);
357 opt->option->name = talloc_strdup(opt, "(unknown)");
359 opt->option->id = talloc_asprintf(opt->option, "%s#%s",
360 script->ctx->device->device->id,
361 id ? id : opt->option->name);
365 statements_execute(script, st->statements);
367 if (!opt->boot_image)
370 opt->option->is_default = option_is_default(script, opt, id);
372 list_add_tail(&script->options, &opt->list);
379 static int function_invoke(struct grub2_script *script,
380 void *data, int argc, char **argv)
382 struct grub2_statement_function *fn = data;
386 /* set positional parameters */
387 for (i = 1; i < argc; i++) {
388 name = talloc_asprintf(script, "%d", i);
389 script_env_set(script, name, argv[i]);
392 return statements_execute(script, fn->body);
395 int statement_function_execute(struct grub2_script *script,
396 struct grub2_statement *statement)
398 struct grub2_statement_function *st = to_stmt_function(statement);
401 if (st->name->type == GRUB2_WORD_VAR)
402 name = expand_var(script, st->name);
404 name = st->name->text;
406 script_register_function(script, name, function_invoke, st);
411 int statement_for_execute(struct grub2_script *script,
412 struct grub2_statement *statement)
414 struct grub2_statement_for *st = to_stmt_for(statement);
418 if (st->var->type == GRUB2_WORD_VAR)
419 expand_var(script, st->var);
420 varname = st->var->text;
422 process_expansions(script, st->list);
424 for (i = 0; i < st->list->argc; ++i) {
425 script_env_set(script, varname, st->list->argv[i]);
426 rc = statements_execute(script, st->body);
432 static void init_env(struct grub2_script *script)
434 struct env_entry *env;
437 list_init(&script->environment);
439 /* use location of the parsed config file to determine the prefix */
440 env = talloc(script, struct env_entry);
443 if (script->filename) {
444 sep = strrchr(script->filename, '/');
446 prefix = talloc_strndup(env, script->filename,
447 sep - script->filename);
450 script_env_set(script, "prefix", prefix ? : default_prefix);
454 /* establish feature settings */
455 script_env_set(script, "feature_menuentry_id", "y");
458 void script_register_function(struct grub2_script *script,
459 const char *name, grub2_function fn,
462 struct grub2_symtab_entry *entry;
464 entry = talloc(script, struct grub2_symtab_entry);
468 list_add(&script->symtab, &entry->list);
471 static void set_fallback_default(struct grub2_script *script)
473 struct discover_boot_option *opt, *first = NULL;
474 bool have_default = false;
476 list_for_each_entry(&script->options, opt, list) {
479 have_default = have_default || opt->option->is_default;
482 if (!have_default && first) {
483 const char *env = script_env_get(script, "default");
485 pb_log("grub: no explicit default (env default=%s), "
486 "falling back to first option (%s)\n",
487 env ?: "unset", first->option->name);
489 first->option->is_default = true;
493 void script_execute(struct grub2_script *script)
495 struct discover_boot_option *opt, *tmp;
501 statements_execute(script, script->statements);
503 set_fallback_default(script);
505 list_for_each_entry_safe(&script->options, opt, tmp, list)
506 discover_context_add_boot_option(script->ctx, opt);
508 /* Our option list will be invalid, as we've added all options to the
509 * discover context */
510 list_init(&script->options);
513 struct grub2_script *create_script(struct grub2_parser *parser,
514 struct discover_context *ctx)
516 struct grub2_script *script;
518 script = talloc_zero(parser, struct grub2_script);
522 list_init(&script->symtab);
523 list_init(&script->options);
524 register_builtins(script);