3f5bc236a66911fb54111a8ec7d3916c68454cca
[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         char *pos;
239         int rc;
240
241         if (!st->argv)
242                 return 0;
243
244         process_expansions(script, st->argv);
245
246         if (!st->argv->argc)
247                 return 0;
248
249         /* is this a var=value assignment? */
250         pos = strchr(st->argv->argv[0], '=');
251         if (pos) {
252                 char *name, *value;
253                 name = st->argv->argv[0];
254                 name = talloc_strndup(st, name, pos - name);
255                 value = pos + 1;
256                 script_env_set(script, name, value);
257                 return 0;
258         }
259
260         entry = script_lookup_function(script, st->argv->argv[0]);
261         if (!entry) {
262                 fprintf(stderr, "undefined function '%s'\n", st->argv->argv[0]);
263                 return 1;
264         }
265
266         rc = entry->fn(script, entry->data, st->argv->argc, st->argv->argv);
267
268         return rc;
269 }
270
271 int statement_block_execute(struct grub2_script *script,
272                 struct grub2_statement *statement)
273 {
274         struct grub2_statement_block *st = to_stmt_block(statement);
275         return statements_execute(script, st->statements);
276 }
277
278 /* returns 0 if the statement was executed, 1 otherwise */
279 static int statement_conditional_execute(struct grub2_script *script,
280                 struct grub2_statement *statement, bool *executed)
281 {
282         struct grub2_statement_conditional *st = to_stmt_conditional(statement);
283         int rc;
284
285         rc = st->condition->exec(script, st->condition);
286         *executed = (!rc);
287         if (*executed)
288                 rc = statements_execute(script, st->statements);
289
290         return rc;
291 }
292
293 int statement_if_execute(struct grub2_script *script,
294                 struct grub2_statement *statement)
295 {
296         struct grub2_statement_if *st = to_stmt_if(statement);
297         struct grub2_statement *conditional;
298         bool executed;
299         int rc;
300
301         list_for_each_entry(&st->conditionals->list, conditional, list) {
302                 rc = statement_conditional_execute(script,
303                                 conditional, &executed);
304                 if (executed)
305                         break;
306         }
307
308         if (!executed && st->else_case)
309                 rc = statements_execute(script, st->else_case);
310
311         return rc;
312 }
313
314 int statement_menuentry_execute(struct grub2_script *script,
315                 struct grub2_statement *statement)
316 {
317         struct grub2_statement_menuentry *st = to_stmt_menuentry(statement);
318         struct discover_boot_option *opt;
319
320         process_expansions(script, st->argv);
321
322         opt = discover_boot_option_create(script->ctx, script->ctx->device);
323         if (st->argv->argc > 0) {
324                 opt->option->name = talloc_strdup(opt, st->argv->argv[0]);
325         } else {
326                 opt->option->name = talloc_strdup(opt, "(unknown)");
327         }
328         opt->option->id = talloc_asprintf(opt->option, "%s#%s",
329                         script->ctx->device->device->id,
330                         opt->option->name);
331
332         script->opt = opt;
333
334         statements_execute(script, st->statements);
335
336         discover_context_add_boot_option(script->ctx, opt);
337         script->opt = NULL;
338
339         return 0;
340 }
341
342 static int function_invoke(struct grub2_script *script,
343                 void *data, int argc, char **argv)
344 {
345         struct grub2_statement_function *fn = data;
346         char *name;
347         int i;
348
349         /* set positional parameters */
350         for (i = 0; i < argc; i++) {
351                 name = talloc_asprintf(script, "$%d", i);
352                 script_env_set(script, name, argv[i]);
353         }
354
355         return statements_execute(script, fn->body);
356 }
357
358 int statement_function_execute(struct grub2_script *script,
359                 struct grub2_statement *statement)
360 {
361         struct grub2_statement_function *st = to_stmt_function(statement);
362         const char *name;
363
364         if (st->name->type == GRUB2_WORD_VAR)
365                 expand_var(script, st->name);
366
367         name = st->name->text;
368         script_register_function(script, name, function_invoke, st);
369
370         return 0;
371 }
372
373 static void init_env(struct grub2_script *script)
374 {
375         struct env_entry *env;
376
377         list_init(&script->environment);
378
379         env = talloc(script, struct env_entry);
380         env->name = talloc_strdup(env, "prefix");
381         env->value = talloc_strdup(env, "/");
382
383         list_add(&script->environment, &env->list);
384 }
385
386 void script_register_function(struct grub2_script *script,
387                 const char *name, grub2_function fn,
388                 void *data)
389 {
390         struct grub2_symtab_entry *entry;
391
392         entry = talloc(script, struct grub2_symtab_entry);
393         entry->fn = fn;
394         entry->name = name;
395         entry->data = data;
396         list_add(&script->symtab, &entry->list);
397 }
398
399
400 void script_execute(struct grub2_script *script)
401 {
402         statements_execute(script, script->statements);
403 }
404
405 struct grub2_script *create_script(struct grub2_parser *parser,
406                 struct discover_context *ctx)
407 {
408         struct grub2_script *script;
409
410         script = talloc(parser, struct grub2_script);
411
412         init_env(script);
413         script->ctx = ctx;
414         script->opt = NULL;
415
416         list_init(&script->symtab);
417         register_builtins(script);
418
419         return script;
420 }
421