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