]> git.ozlabs.org Git - petitboot/blob - discover/grub2/blscfg.c
Fix pb-discover segfaults caused by list corruption.
[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         char* args_sigfile_default = talloc_asprintf(opt,
152                 "%s.cmdline.sig", state->image);
153         opt->args_sig_file = create_grub2_resource(opt, conf->dc->device,
154                                                 root, args_sigfile_default);
155         talloc_free(args_sigfile_default);
156
157         option->is_default = option_is_default(state, option);
158
159         list_add_tail(&state->script->options, &opt->list);
160         state->script->n_options++;
161
162         device_handler_status_dev_info(dc->handler, dc->device,
163                                        _("Created menu entry from BLS file %s"),
164                                        state->filename);
165 }
166
167 static int bls_filter(const struct dirent *ent)
168 {
169         int offset = strlen(ent->d_name) - strlen(".conf");
170
171         if (offset < 0)
172                 return 0;
173
174         return strncmp(ent->d_name + offset, ".conf", strlen(".conf")) == 0;
175 }
176
177 static int bls_sort(const struct dirent **ent_a, const struct dirent **ent_b)
178 {
179         return strverscmp((*ent_a)->d_name, (*ent_b)->d_name);
180 }
181
182 int builtin_blscfg(struct grub2_script *script,
183                 void *data __attribute__((unused)),
184                 int argc __attribute__((unused)),
185                 char *argv[] __attribute__((unused)));
186
187 int builtin_blscfg(struct grub2_script *script,
188                 void *data __attribute__((unused)),
189                 int argc __attribute__((unused)),
190                 char *argv[] __attribute__((unused)))
191 {
192         unsigned int current_idx = script->n_options;
193         struct discover_context *dc = script->ctx;
194         struct dirent **bls_entries;
195         struct conf_context *conf;
196         struct bls_state *state;
197         char *buf, *filename;
198         const char *blsdir;
199         int n, len, rc = -1;
200
201         conf = talloc_zero(dc, struct conf_context);
202         if (!conf)
203                 return rc;
204
205         conf->dc = dc;
206         conf->get_pair = conf_get_pair_space;
207         conf->process_pair = bls_process_pair;
208         conf->finish = bls_finish;
209
210         blsdir = script_env_get(script, "blsdir");
211         if (!blsdir)
212                 blsdir = BLS_DIR;
213
214         n = parser_scandir(dc, blsdir, &bls_entries, bls_filter, bls_sort);
215         if (n <= 0)
216                 goto err;
217
218         while (n--) {
219                 filename = talloc_asprintf(dc, "%s/%s", blsdir,
220                                            bls_entries[n]->d_name);
221                 if (!filename)
222                         break;
223
224                 state = talloc_zero(conf, struct bls_state);
225                 if (!state)
226                         break;
227
228                 state->opt = discover_boot_option_create(dc, dc->device);
229                 if (!state->opt)
230                         break;
231
232                 state->script = script;
233                 state->filename = filename;
234                 state->idx = current_idx++;
235                 conf->parser_info = state;
236
237                 rc = parser_request_file(dc, dc->device, filename, &buf, &len);
238                 if (rc)
239                         break;
240
241                 conf_parse_buf(conf, buf, len);
242
243                 talloc_free(buf);
244                 talloc_free(state);
245                 talloc_free(filename);
246                 free(bls_entries[n]);
247         }
248
249         if (n > 0) {
250                 device_handler_status_dev_info(dc->handler, dc->device,
251                                                _("Scanning %s failed"),
252                                                BLS_DIR);
253                 do {
254                         free(bls_entries[n]);
255                 } while (n-- > 0);
256         }
257
258         free(bls_entries);
259 err:
260         talloc_free(conf);
261         return rc;
262 }