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