]> git.ozlabs.org Git - petitboot/blob - discover/grub2/blscfg.c
discover/pxe-parser: Parse simple iPXE scripts
[petitboot] / discover / grub2 / blscfg.c
1
2 #define _GNU_SOURCE
3
4 #include <assert.h>
5 #include <ctype.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <dirent.h>
9
10 #include <log/log.h>
11 #include <file/file.h>
12 #include <talloc/talloc.h>
13 #include <i18n/i18n.h>
14
15 #include "grub2.h"
16 #include "discover/parser-conf.h"
17 #include "discover/parser.h"
18
19 static const char *const bls_dirs[] = {
20         "/loader/entries",
21         "/boot/loader/entries",
22         NULL
23 };
24
25 struct bls_state {
26         struct discover_boot_option *opt;
27         struct grub2_script *script;
28         unsigned int idx;
29         const char *filename;
30         const char *title;
31         const char *version;
32         const char *machine_id;
33         const char *image;
34         const char *initrd;
35         const char *dtb;
36 };
37
38 static char *field_append(struct bls_state *state, int type, char *buffer,
39                           char *start, char *end)
40 {
41         char *temp = talloc_strndup(state, start, end - start + 1);
42         const char *field = temp;
43
44         if (type == GRUB2_WORD_VAR) {
45                 field = script_env_get(state->script, temp);
46                 if (!field)
47                         return buffer;
48         }
49
50         if (!buffer)
51                 buffer = talloc_strdup(state->opt, field);
52         else
53                 buffer = talloc_asprintf_append(buffer, "%s", field);
54
55         return buffer;
56 }
57
58 static char *expand_field(struct bls_state *state, char *value)
59 {
60         char *buffer = NULL;
61         char *start = value;
62         char *end = value;
63         int type = GRUB2_WORD_TEXT;
64
65         while (*value) {
66                 if (*value == '$') {
67                         if (start != end) {
68                                 buffer = field_append(state, type, buffer,
69                                                       start, end);
70                                 if (!buffer)
71                                         return NULL;
72                         }
73
74                         type = GRUB2_WORD_VAR;
75                         start = value + 1;
76                 } else if (type == GRUB2_WORD_VAR) {
77                         if (!isalnum(*value) && *value != '_') {
78                                 buffer = field_append(state, type, buffer,
79                                                       start, end);
80                                 type = GRUB2_WORD_TEXT;
81                                 start = value;
82                         }
83                 }
84
85                 end = value;
86                 value++;
87         }
88
89         if (start != end) {
90                 buffer = field_append(state, type, buffer,
91                                       start, end);
92                 if (!buffer)
93                         return NULL;
94         }
95
96         return buffer;
97 }
98
99 static void bls_process_pair(struct conf_context *conf, const char *name,
100                              char *value)
101 {
102         struct bls_state *state = conf->parser_info;
103         struct discover_boot_option *opt = state->opt;
104         struct boot_option *option = opt->option;
105
106         if (streq(name, "title")) {
107                 state->title = expand_field(state, value);
108                 return;
109         }
110
111         if (streq(name, "version")) {
112                 state->version = expand_field(state, value);
113                 return;
114         }
115
116         if (streq(name, "machine-id")) {
117                 state->machine_id = expand_field(state, value);
118                 return;
119         }
120
121         if (streq(name, "linux")) {
122                 state->image = expand_field(state, value);
123                 return;
124         }
125
126         if (streq(name, "initrd")) {
127                 state->initrd = expand_field(state, value);
128                 return;
129         }
130
131         if (streq(name, "devicetree")) {
132                 state->dtb = expand_field(state, value);
133                 return;
134         }
135
136         if (streq(name, "options")) {
137                 option->boot_args = expand_field(state, value);
138                 return;
139         }
140 }
141
142 static bool option_is_default(struct bls_state *state,
143                               struct boot_option *option)
144 {
145         unsigned int idx;
146         const char *var;
147         char *end;
148
149         var = script_env_get(state->script, "default");
150         if (!var)
151                 return false;
152
153         if (!strcmp(var, option->id))
154                 return true;
155
156         if (!strcmp(var, option->name))
157                 return true;
158
159         idx = strtoul(var, &end, 10);
160         return end != var && *end == '\0' && idx == state->idx;
161 }
162
163 static void bls_finish(struct conf_context *conf)
164 {
165         struct bls_state *state = conf->parser_info;
166         struct discover_context *dc = conf->dc;
167         struct discover_boot_option *opt = state->opt;
168         struct boot_option *option = opt->option;
169         const char *root;
170         char *filename;
171
172         if (!state->image) {
173                 device_handler_status_dev_info(dc->handler, dc->device,
174                                                _("linux field not found in %s"),
175                                                state->filename);
176                 return;
177         }
178
179         filename = basename(state->filename);
180         filename[strlen(filename) - strlen(".conf")] = '\0';
181
182         option->id = talloc_strdup(option, filename);
183
184         if (state->title)
185                 option->name = talloc_strdup(option, state->title);
186         else if (state->machine_id && state->version)
187                 option->name = talloc_asprintf(option, "%s %s",
188                                                state->machine_id,
189                                                state->version);
190         else if (state->version)
191                 option->name = talloc_strdup(option, state->version);
192         else
193                 option->name = talloc_strdup(option, state->image);
194
195         root = script_env_get(state->script, "root");
196
197         opt->boot_image = create_grub2_resource(opt, conf->dc->device,
198                                                 root, state->image);
199
200         if (state->initrd)
201                 opt->initrd = create_grub2_resource(opt, conf->dc->device,
202                                                     root, state->initrd);
203
204         if (state->dtb)
205                 opt->dtb = create_grub2_resource(opt, conf->dc->device,
206                                                  root, state->dtb);
207
208         char* args_sigfile_default = talloc_asprintf(opt,
209                 "%s.cmdline.sig", state->image);
210         opt->args_sig_file = create_grub2_resource(opt, conf->dc->device,
211                                                 root, args_sigfile_default);
212         talloc_free(args_sigfile_default);
213
214         option->is_default = option_is_default(state, option);
215
216         list_add_tail(&state->script->options, &opt->list);
217         state->script->n_options++;
218
219         device_handler_status_dev_info(dc->handler, dc->device,
220                                        _("Created menu entry from BLS file %s"),
221                                        state->filename);
222 }
223
224 static int bls_filter(const struct dirent *ent)
225 {
226         int offset = strlen(ent->d_name) - strlen(".conf");
227
228         if (offset < 0)
229                 return 0;
230
231         return strncmp(ent->d_name + offset, ".conf", strlen(".conf")) == 0;
232 }
233
234 static int bls_sort(const struct dirent **ent_a, const struct dirent **ent_b)
235 {
236         return strverscmp((*ent_a)->d_name, (*ent_b)->d_name);
237 }
238
239 int builtin_blscfg(struct grub2_script *script,
240                 void *data __attribute__((unused)),
241                 int argc __attribute__((unused)),
242                 char *argv[] __attribute__((unused)));
243
244 int builtin_blscfg(struct grub2_script *script,
245                 void *data __attribute__((unused)),
246                 int argc __attribute__((unused)),
247                 char *argv[] __attribute__((unused)))
248 {
249         unsigned int current_idx = script->n_options;
250         struct discover_context *dc = script->ctx;
251         struct dirent **bls_entries;
252         struct conf_context *conf;
253         struct bls_state *state;
254         char *buf, *filename;
255         const char * const *dir;
256         const char *blsdir;
257         int n, len, rc = -1;
258         struct stat statbuf;
259
260         conf = talloc_zero(dc, struct conf_context);
261         if (!conf)
262                 return rc;
263
264         conf->dc = dc;
265         conf->get_pair = conf_get_pair_space;
266         conf->process_pair = bls_process_pair;
267         conf->finish = bls_finish;
268
269         blsdir = script_env_get(script, "blsdir");
270         if (!blsdir)
271                 for (dir = bls_dirs; *dir; dir++)
272                         if (!parser_stat_path(dc, dc->device, *dir, &statbuf)) {
273                                 blsdir = *dir;
274                                 break;
275                         }
276
277         if (!blsdir) {
278                 device_handler_status_dev_info(dc->handler, dc->device,
279                                                _("BLS directory wasn't found"));
280                 goto err;
281         }
282
283         n = parser_scandir(dc, blsdir, &bls_entries, bls_filter, bls_sort);
284         if (n <= 0)
285                 goto err;
286
287         while (n--) {
288                 filename = talloc_asprintf(dc, "%s/%s", blsdir,
289                                            bls_entries[n]->d_name);
290                 if (!filename)
291                         break;
292
293                 state = talloc_zero(conf, struct bls_state);
294                 if (!state)
295                         break;
296
297                 state->opt = discover_boot_option_create(dc, dc->device);
298                 if (!state->opt)
299                         break;
300
301                 state->script = script;
302                 state->filename = filename;
303                 state->idx = current_idx++;
304                 conf->parser_info = state;
305
306                 rc = parser_request_file(dc, dc->device, filename, &buf, &len);
307                 if (rc)
308                         break;
309
310                 conf_parse_buf(conf, buf, len);
311
312                 talloc_free(buf);
313                 talloc_free(state);
314                 talloc_free(filename);
315                 free(bls_entries[n]);
316         }
317
318         if (n > 0) {
319                 device_handler_status_dev_info(dc->handler, dc->device,
320                                                _("Scanning %s failed"),
321                                                blsdir);
322                 do {
323                         free(bls_entries[n]);
324                 } while (n-- > 0);
325         }
326
327         free(bls_entries);
328 err:
329         talloc_free(conf);
330         return rc;
331 }