discover/grub2: Clean up error-handling for grub2 parser & lexer
[petitboot] / discover / grub2 / script.c
1
2 #include <sys/types.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 #include <log/log.h>
7 #include <types/types.h>
8 #include <talloc/talloc.h>
9
10 #include "grub2.h"
11
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_conditional(stmt) \
23         container_of(stmt, struct grub2_statement_conditional, st)
24
25 struct env_entry {
26         const char              *name;
27         const char              *value;
28         struct list_item        list;
29 };
30
31 struct grub2_symtab_entry {
32         const char              *name;
33         grub2_function          fn;
34         void                    *data;
35         struct list_item        list;
36 };
37
38 static struct grub2_symtab_entry *script_lookup_function(
39                 struct grub2_script *script, const char *name)
40 {
41         struct grub2_symtab_entry *entry;
42
43         list_for_each_entry(&script->symtab, entry, list) {
44                 if (!strcmp(entry->name, name))
45                         return entry;
46         }
47
48         return NULL;
49 }
50
51 const char *script_env_get(struct grub2_script *script, const char *name)
52 {
53         struct env_entry *entry;
54
55         list_for_each_entry(&script->environment, entry, list)
56                 if (!strcmp(entry->name, name))
57                         return entry->value;
58
59         return NULL;
60 }
61
62 void script_env_set(struct grub2_script *script,
63                 const char *name, const char *value)
64 {
65         struct env_entry *e, *entry = NULL;
66
67         list_for_each_entry(&script->environment, e, list) {
68                 if (!strcmp(e->name, name)) {
69                         entry = e;
70                         break;
71                 }
72         }
73
74         if (!entry) {
75                 entry = talloc(script, struct env_entry);
76                 entry->name = name;
77                 list_add(&script->environment, &entry->list);
78         }
79
80         entry->value = value;
81 }
82
83 static bool expand_var(struct grub2_script *script, struct grub2_word *word)
84 {
85         const char *val;
86
87         val = script_env_get(script, word->name);
88         if (!val)
89                 val = "";
90
91         word->text = talloc_strdup(script, val);
92
93         return true;
94 }
95
96 static bool is_delim(char c)
97 {
98         return c == ' ' || c == '\t';
99 }
100
101 static bool option_is_default(struct grub2_script *script,
102                 struct discover_boot_option *opt)
103 {
104         unsigned int default_idx;
105         const char *var;
106         char *end;
107
108         var = script_env_get(script, "default");
109         if (!var)
110                 return false;
111
112         default_idx = strtoul(var, &end, 10);
113         if (end != var && *end == '\0')
114                 return default_idx == script->n_options;
115
116         return !strcmp(opt->option->name, var);
117 }
118
119 /* For non-double-quoted variable expansions, we may need to split the
120  * variable's value into multiple argv items.
121  *
122  * This function sets the word->text to the initial set of non-delimiter chars
123  * in the expanded var value. We then skip any delimiter chars, and (if
124  * required), create the new argv item with the remaining text, and
125  * add it to the argv list, after top_word.
126  */
127 static void process_split(struct grub2_script *script,
128                 struct grub2_word *top_word,
129                 struct grub2_word *word)
130 {
131         int i, len, delim_start = -1, delim_end = -1;
132         struct grub2_word *new_word;
133         char *remainder;
134
135         len = strlen(word->text);
136
137         /* Scan our string for the start of a delim (delim_start), and the
138          * start of any new text (delim_end). */
139         for (i = 0; i < len; i++) {
140                 if (is_delim(word->text[i])) {
141                         if (delim_start == -1)
142                                 delim_start = i;
143                 } else if (delim_start != -1) {
144                         delim_end = i;
145                         break;
146                 }
147         }
148
149         /* No delim? nothing to do. The process_expansions loop will
150          * append this word's text to the top word, if necessary
151          */
152         if (delim_start == -1)
153                 return;
154
155         /* Set this word's text value to the text before the delimiter.
156          * this will get appended to the top word
157          */
158         word->text[delim_start] = '\0';
159
160         /* No trailing text? If there are no following word tokens, we're done.
161          * Otherwise, we need to start a new argv item with those tokens */
162         if (delim_end == -1) {
163                 if (!word->next)
164                         return;
165                 remainder = "";
166         } else {
167                 remainder = word->text + delim_end;
168         }
169
170         new_word = talloc(script, struct grub2_word);
171         new_word->type = GRUB2_WORD_TEXT;
172         /* if there's no trailing text, we know we don't need to re-split */
173         new_word->split = delim_end != -1;
174         new_word->next = word->next;
175         new_word->last = NULL;
176         new_word->text = talloc_strdup(new_word, remainder);
177
178         /* stitch it into the argv list before this word */
179         list_insert_after(&top_word->argv_list,
180                            &new_word->argv_list);
181
182         /* terminate this word */
183         word->next = NULL;
184 }
185
186 /* iterate through the words in an argv_list, looking for GRUB2_WORD_VAR
187  * expansions.
188  *
189  * Once that's done, we may (if split == true) have to split the word to create
190  * new argv items
191  */
192 static void process_expansions(struct grub2_script *script,
193                 struct grub2_argv *argv)
194 {
195         struct grub2_word *top_word, *word;
196         int i;
197
198         argv->argc = 0;
199
200         list_for_each_entry(&argv->words, top_word, argv_list) {
201                 argv->argc++;
202
203                 /* expand vars and squash the list of words into the head
204                  * of the argv word list */
205                 for (word = top_word; word; word = word->next) {
206
207                         /* if it's a variable, perform the substitution */
208                         if (word->type == GRUB2_WORD_VAR) {
209                                 expand_var(script, word);
210                                 word->type = GRUB2_WORD_TEXT;
211                         }
212
213                         /* split; this will potentially insert argv
214                          * entries after top_word. */
215                         if (word->split)
216                                 process_split(script, top_word, word);
217
218                         /* accumulate word text into the top word, so
219                          * we end up with a shallow tree of argv data */
220                         /* todo: don't do this in process_split */
221                         if (word != top_word) {
222                                 top_word->text = talloc_asprintf_append(
223                                                         top_word->text,
224                                                         "%s", word->text);
225                         }
226
227
228                 }
229                 top_word->next = NULL;
230         }
231
232         /* convert the list to an argv array, to pass to the function */
233         argv->argv = talloc_array(script, char *, argv->argc);
234         i = 0;
235
236         list_for_each_entry(&argv->words, word, argv_list)
237                 argv->argv[i++] = word->text;
238 }
239
240 static int statements_execute(struct grub2_script *script,
241                 struct grub2_statements *stmts)
242 {
243         struct grub2_statement *stmt;
244         int rc = 0;
245
246         list_for_each_entry(&stmts->list, stmt, list) {
247                 if (stmt->exec)
248                         rc = stmt->exec(script, stmt);
249         }
250         return rc;
251 }
252
253 int statement_simple_execute(struct grub2_script *script,
254                 struct grub2_statement *statement)
255 {
256         struct grub2_statement_simple *st = to_stmt_simple(statement);
257         struct grub2_symtab_entry *entry;
258         char *pos;
259         int rc;
260
261         if (!st->argv)
262                 return 0;
263
264         process_expansions(script, st->argv);
265
266         if (!st->argv->argc)
267                 return 0;
268
269         /* is this a var=value assignment? */
270         pos = strchr(st->argv->argv[0], '=');
271         if (pos) {
272                 char *name, *value;
273                 name = st->argv->argv[0];
274                 name = talloc_strndup(st, name, pos - name);
275                 value = pos + 1;
276                 script_env_set(script, name, value);
277                 return 0;
278         }
279
280         entry = script_lookup_function(script, st->argv->argv[0]);
281         if (!entry) {
282                 pb_log("grub2: undefined function '%s'\n", st->argv->argv[0]);
283                 return 1;
284         }
285
286         rc = entry->fn(script, entry->data, st->argv->argc, st->argv->argv);
287
288         return rc;
289 }
290
291 int statement_block_execute(struct grub2_script *script,
292                 struct grub2_statement *statement)
293 {
294         struct grub2_statement_block *st = to_stmt_block(statement);
295         return statements_execute(script, st->statements);
296 }
297
298 /* returns 0 if the statement was executed, 1 otherwise */
299 static int statement_conditional_execute(struct grub2_script *script,
300                 struct grub2_statement *statement, bool *executed)
301 {
302         struct grub2_statement_conditional *st = to_stmt_conditional(statement);
303         int rc;
304
305         rc = st->condition->exec(script, st->condition);
306         *executed = (!rc);
307         if (*executed)
308                 rc = statements_execute(script, st->statements);
309
310         return rc;
311 }
312
313 int statement_if_execute(struct grub2_script *script,
314                 struct grub2_statement *statement)
315 {
316         struct grub2_statement_if *st = to_stmt_if(statement);
317         struct grub2_statement *conditional;
318         bool executed;
319         int rc;
320
321         list_for_each_entry(&st->conditionals->list, conditional, list) {
322                 rc = statement_conditional_execute(script,
323                                 conditional, &executed);
324                 if (executed)
325                         break;
326         }
327
328         if (!executed && st->else_case)
329                 rc = statements_execute(script, st->else_case);
330
331         return rc;
332 }
333
334 int statement_menuentry_execute(struct grub2_script *script,
335                 struct grub2_statement *statement)
336 {
337         struct grub2_statement_menuentry *st = to_stmt_menuentry(statement);
338         struct discover_boot_option *opt;
339
340         process_expansions(script, st->argv);
341
342         opt = discover_boot_option_create(script->ctx, script->ctx->device);
343         if (st->argv->argc > 0) {
344                 opt->option->name = talloc_strdup(opt, st->argv->argv[0]);
345         } else {
346                 opt->option->name = talloc_strdup(opt, "(unknown)");
347         }
348         opt->option->id = talloc_asprintf(opt->option, "%s#%s",
349                         script->ctx->device->device->id,
350                         opt->option->name);
351
352         script->opt = opt;
353
354         statements_execute(script, st->statements);
355
356         opt->option->is_default = option_is_default(script, opt);
357
358         discover_context_add_boot_option(script->ctx, opt);
359         script->n_options++;
360         script->opt = NULL;
361
362         return 0;
363 }
364
365 static int function_invoke(struct grub2_script *script,
366                 void *data, int argc, char **argv)
367 {
368         struct grub2_statement_function *fn = data;
369         char *name;
370         int i;
371
372         /* set positional parameters */
373         for (i = 0; i < argc; i++) {
374                 name = talloc_asprintf(script, "$%d", i);
375                 script_env_set(script, name, argv[i]);
376         }
377
378         return statements_execute(script, fn->body);
379 }
380
381 int statement_function_execute(struct grub2_script *script,
382                 struct grub2_statement *statement)
383 {
384         struct grub2_statement_function *st = to_stmt_function(statement);
385         const char *name;
386
387         if (st->name->type == GRUB2_WORD_VAR)
388                 expand_var(script, st->name);
389
390         name = st->name->text;
391         script_register_function(script, name, function_invoke, st);
392
393         return 0;
394 }
395
396 static void init_env(struct grub2_script *script)
397 {
398         struct env_entry *env;
399
400         list_init(&script->environment);
401
402         env = talloc(script, struct env_entry);
403         env->name = talloc_strdup(env, "prefix");
404         env->value = talloc_strdup(env, "/");
405
406         list_add(&script->environment, &env->list);
407 }
408
409 void script_register_function(struct grub2_script *script,
410                 const char *name, grub2_function fn,
411                 void *data)
412 {
413         struct grub2_symtab_entry *entry;
414
415         entry = talloc(script, struct grub2_symtab_entry);
416         entry->fn = fn;
417         entry->name = name;
418         entry->data = data;
419         list_add(&script->symtab, &entry->list);
420 }
421
422
423 void script_execute(struct grub2_script *script)
424 {
425         statements_execute(script, script->statements);
426 }
427
428 struct grub2_script *create_script(struct grub2_parser *parser,
429                 struct discover_context *ctx)
430 {
431         struct grub2_script *script;
432
433         script = talloc_zero(parser, struct grub2_script);
434
435         init_env(script);
436         script->ctx = ctx;
437
438         list_init(&script->symtab);
439         register_builtins(script);
440
441         return script;
442 }
443