discover/grub: Allow to set a default index for BLS entries
[petitboot] / discover / grub2 / blscfg.c
1
2 #define _GNU_SOURCE
3
4 #include <assert.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <dirent.h>
8
9 #include <log/log.h>
10 #include <file/file.h>
11 #include <talloc/talloc.h>
12 #include <i18n/i18n.h>
13
14 #include "grub2.h"
15 #include "discover/parser-conf.h"
16 #include "discover/parser.h"
17
18 #define BLS_DIR "/loader/entries"
19
20 struct bls_state {
21         struct discover_boot_option *opt;
22         struct grub2_script *script;
23         unsigned int idx;
24         const char *filename;
25         const char *title;
26         const char *version;
27         const char *machine_id;
28         const char *image;
29         const char *initrd;
30         const char *dtb;
31 };
32
33 static void bls_process_pair(struct conf_context *conf, const char *name,
34                              char *value)
35 {
36         struct bls_state *state = conf->parser_info;
37         struct discover_boot_option *opt = state->opt;
38         struct boot_option *option = opt->option;
39         const char *boot_args;
40
41         if (streq(name, "title")) {
42                 state->title = talloc_strdup(state, value);
43                 return;
44         }
45
46         if (streq(name, "version")) {
47                 state->version = talloc_strdup(state, value);
48                 return;
49         }
50
51         if (streq(name, "machine-id")) {
52                 state->machine_id = talloc_strdup(state, value);
53                 return;
54         }
55
56         if (streq(name, "linux")) {
57                 state->image = talloc_strdup(state, value);
58                 return;
59         }
60
61         if (streq(name, "initrd")) {
62                 state->initrd = talloc_strdup(state, value);
63                 return;
64         }
65
66         if (streq(name, "devicetree")) {
67                 state->dtb = talloc_strdup(state, value);
68                 return;
69         }
70
71         if (streq(name, "options")) {
72                 if (value[0] == '$') {
73                         boot_args = script_env_get(state->script, value + 1);
74                         if (!boot_args)
75                                 return;
76
77                         option->boot_args = talloc_strdup(opt, boot_args);
78                 } else {
79                         option->boot_args = talloc_strdup(opt, value);
80                 }
81                 return;
82         }
83 }
84
85 static bool option_is_default(struct bls_state *state,
86                               struct boot_option *option)
87 {
88         unsigned int idx;
89         const char *var;
90         char *end;
91
92         var = script_env_get(state->script, "default");
93         if (!var)
94                 return false;
95
96         if (!strcmp(var, option->id))
97                 return true;
98
99         if (!strcmp(var, option->name))
100                 return true;
101
102         idx = strtoul(var, &end, 10);
103         return end != var && *end == '\0' && idx == state->idx;
104 }
105
106 static void bls_finish(struct conf_context *conf)
107 {
108         struct bls_state *state = conf->parser_info;
109         struct discover_context *dc = conf->dc;
110         struct discover_boot_option *opt = state->opt;
111         struct boot_option *option = opt->option;
112         const char *root;
113         char *filename;
114
115         if (!state->image) {
116                 device_handler_status_dev_info(dc->handler, dc->device,
117                                                _("linux field not found in %s"),
118                                                state->filename);
119                 return;
120         }
121
122         filename = basename(state->filename);
123         filename[strlen(filename) - strlen(".conf")] = '\0';
124
125         option->id = talloc_strdup(option, filename);
126
127         if (state->title)
128                 option->name = talloc_strdup(option, state->title);
129         else if (state->machine_id && state->version)
130                 option->name = talloc_asprintf(option, "%s %s",
131                                                state->machine_id,
132                                                state->version);
133         else if (state->version)
134                 option->name = talloc_strdup(option, state->version);
135         else
136                 option->name = talloc_strdup(option, state->image);
137
138         root = script_env_get(state->script, "root");
139
140         opt->boot_image = create_grub2_resource(opt, conf->dc->device,
141                                                 root, state->image);
142
143         if (state->initrd)
144                 opt->initrd = create_grub2_resource(opt, conf->dc->device,
145                                                     root, state->initrd);
146
147         if (state->dtb)
148                 opt->dtb = create_grub2_resource(opt, conf->dc->device,
149                                                  root, state->dtb);
150
151         option->is_default = option_is_default(state, option);
152
153         list_add_tail(&state->script->options, &opt->list);
154         state->script->n_options++;
155
156         device_handler_status_dev_info(dc->handler, dc->device,
157                                        _("Created menu entry from BLS file %s"),
158                                        state->filename);
159 }
160
161 static int bls_filter(const struct dirent *ent)
162 {
163         int offset = strlen(ent->d_name) - strlen(".conf");
164
165         if (offset < 0)
166                 return 0;
167
168         return strncmp(ent->d_name + offset, ".conf", strlen(".conf")) == 0;
169 }
170
171 static int bls_sort(const struct dirent **ent_a, const struct dirent **ent_b)
172 {
173         return strverscmp((*ent_a)->d_name, (*ent_b)->d_name);
174 }
175
176 int builtin_blscfg(struct grub2_script *script,
177                 void *data __attribute__((unused)),
178                 int argc __attribute__((unused)),
179                 char *argv[] __attribute__((unused)));
180
181 int builtin_blscfg(struct grub2_script *script,
182                 void *data __attribute__((unused)),
183                 int argc __attribute__((unused)),
184                 char *argv[] __attribute__((unused)))
185 {
186         unsigned int current_idx = script->n_options;
187         struct discover_context *dc = script->ctx;
188         struct dirent **bls_entries;
189         struct conf_context *conf;
190         struct bls_state *state;
191         char *buf, *filename;
192         const char *blsdir;
193         int n, len, rc = -1;
194
195         conf = talloc_zero(dc, struct conf_context);
196         if (!conf)
197                 return rc;
198
199         conf->dc = dc;
200         conf->get_pair = conf_get_pair_space;
201         conf->process_pair = bls_process_pair;
202         conf->finish = bls_finish;
203
204         blsdir = script_env_get(script, "blsdir");
205         if (!blsdir)
206                 blsdir = BLS_DIR;
207
208         n = parser_scandir(dc, blsdir, &bls_entries, bls_filter, bls_sort);
209         if (n <= 0)
210                 goto err;
211
212         while (n--) {
213                 filename = talloc_asprintf(dc, "%s/%s", blsdir,
214                                            bls_entries[n]->d_name);
215                 if (!filename)
216                         break;
217
218                 state = talloc_zero(conf, struct bls_state);
219                 if (!state)
220                         break;
221
222                 state->opt = discover_boot_option_create(dc, dc->device);
223                 if (!state->opt)
224                         break;
225
226                 state->script = script;
227                 state->filename = filename;
228                 state->idx = current_idx++;
229                 conf->parser_info = state;
230
231                 rc = parser_request_file(dc, dc->device, filename, &buf, &len);
232                 if (rc)
233                         break;
234
235                 conf_parse_buf(conf, buf, len);
236
237                 talloc_free(buf);
238                 talloc_free(state);
239                 talloc_free(filename);
240                 free(bls_entries[n]);
241         }
242
243         if (n > 0) {
244                 device_handler_status_dev_info(dc->handler, dc->device,
245                                                _("Scanning %s failed"),
246                                                BLS_DIR);
247                 do {
248                         free(bls_entries[n]);
249                 } while (n-- > 0);
250         }
251
252         free(bls_entries);
253 err:
254         talloc_free(conf);
255         return rc;
256 }