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