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