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