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