630ea332bdac14368a1c60be892bb068369bbffe
[petitboot] / discover / grub2-parser.c
1 /*
2  *  Copyright Geoff Levand <geoff@infradead.org>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; version 2 of the License.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17
18 #if defined(HAVE_CONFIG_H)
19 #include "config.h"
20 #endif
21
22 #define _GNU_SOURCE
23
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "log/log.h"
29 #include "talloc/talloc.h"
30 #include "types/types.h"
31 #include "parser-conf.h"
32 #include "parser-utils.h"
33 #include "paths.h"
34 #include "resource.h"
35
36 struct grub2_root {
37         char *uuid;
38 };
39
40 struct grub2_state {
41         struct discover_boot_option *opt;
42         int default_idx;
43         int cur_idx;
44         char *desc_image;
45         char *desc_initrd;
46         const char *const *known_names;
47         struct grub2_root *root;
48 };
49
50 struct grub2_resource_info {
51         struct grub2_root *root;
52         char *path;
53 };
54
55 /* we use slightly different resources for grub2 */
56 static struct resource *create_grub2_resource(void *ctx,
57                 struct discover_device *orig_device,
58                 struct grub2_root *root, const char *path)
59 {
60         struct grub2_resource_info *info;
61         struct resource *res;
62
63         res = talloc(ctx, struct resource);
64
65         if (root) {
66                 info = talloc(res, struct grub2_resource_info);
67                 info->root = root;
68                 talloc_reference(info, root);
69                 info->path = talloc_strdup(info, path);
70
71                 res->resolved = false;
72                 res->info = info;
73
74         } else
75                 resolve_resource_against_device(res, orig_device, path);
76
77         return res;
78 }
79
80 static bool resolve_grub2_resource(struct device_handler *handler,
81                 struct resource *res)
82 {
83         struct grub2_resource_info *info = res->info;
84         struct discover_device *dev;
85
86         assert(!res->resolved);
87
88         dev = device_lookup_by_uuid(handler, info->root->uuid);
89
90         if (!dev)
91                 return false;
92
93         resolve_resource_against_device(res, dev, info->path);
94         talloc_free(info);
95
96         return true;
97 }
98
99 static bool current_option_is_default(struct grub2_state *state)
100 {
101         if (state->default_idx < 0)
102                 return false;
103         return state->cur_idx == state->default_idx;
104 }
105
106 static void grub2_finish(struct conf_context *conf)
107 {
108         struct device *dev = conf->dc->device->device;
109         struct grub2_state *state = conf->parser_info;
110         struct boot_option *opt;
111
112         if (!state->desc_image) {
113                 pb_log("%s: %s: no image found\n", __func__, dev->id);
114                 return;
115         }
116
117         assert(state->opt);
118         opt = state->opt->option;
119
120         assert(opt);
121         assert(opt->name);
122         assert(opt->boot_args);
123
124         opt->description = talloc_asprintf(opt, "%s %s %s",
125                 state->desc_image,
126                 (state->desc_initrd ? state->desc_initrd : ""),
127                 opt->boot_args);
128
129         talloc_free(state->desc_initrd);
130         state->desc_initrd = NULL;
131
132         conf_strip_str(opt->boot_args);
133         conf_strip_str(opt->description);
134
135         state->opt->option->is_default = current_option_is_default(state);
136
137         discover_context_add_boot_option(conf->dc, state->opt);
138
139         state->opt = NULL;
140         state->cur_idx++;
141 }
142
143 static void grub2_process_pair(struct conf_context *conf, const char *name,
144                 char *value)
145 {
146         struct device *dev = conf->dc->device->device;
147         struct grub2_state *state = conf->parser_info;
148         struct discover_boot_option *opt = state->opt;
149
150         if (!name || !conf_param_in_list(state->known_names, name))
151                 return;
152
153         if (streq(name, "menuentry")) {
154                 /* complete any existing option... */
155                 if (state->opt)
156                         grub2_finish(conf);
157
158                 /* ... then start the new one */
159                 opt = discover_boot_option_create(conf->dc, conf->dc->device);
160                 opt->option->boot_args = talloc_strdup(opt->option, "");
161
162                 value = strtok(value, "\'{\"");
163
164                 opt->option->id = talloc_asprintf(opt->option,
165                                         "%s#%s", dev->id, value);
166                 opt->option->name = talloc_strdup(opt->option, value);
167                 opt->option->boot_args = talloc_strdup(opt, "");
168
169                 state->opt = opt;
170
171                 return;
172         }
173
174         if (streq(name, "linux") || streq(name, "linux16")) {
175                 char *sep;
176
177                 sep = strchr(value, ' ');
178
179                 if (sep)
180                         *sep = 0;
181
182                 opt->boot_image = create_grub2_resource(opt, conf->dc->device,
183                                         state->root, value);
184                 state->desc_image = talloc_strdup(opt, value);
185
186                 if (sep)
187                         opt->option->boot_args = talloc_strdup(opt, sep + 1);
188
189                 return;
190         }
191
192         if (streq(name, "initrd")) {
193                 opt->initrd = create_grub2_resource(opt, conf->dc->device,
194                                         state->root, value);
195                 state->desc_initrd = talloc_asprintf(state, "initrd=%s",
196                         value);
197                 return;
198         }
199
200         if (streq(name, "search")) {
201                 struct grub2_root *root;
202                 char *uuid;
203
204                 if (!strstr(value, "--set=root")) {
205                         pb_log("%s: no root\n", __func__);
206                         return;
207                 }
208
209                 /* The UUID should be the last argument to the search command.
210                  * FIXME: this is a little fragile; would be nice to have some
211                  * parser helpers to deal with "command args" parsing
212                  */
213                 uuid = strrchr(value, ' ');
214                 if (!uuid)
215                         return;
216
217                 uuid++;
218
219                 if (state->root)
220                         talloc_unlink(state, state->root);
221
222                 root = talloc(state, struct grub2_root);
223                 root->uuid = talloc_strdup(root, uuid);
224                 state->root = root;
225                 return;
226         }
227
228         if (streq(name, "set")) {
229                 char *sep, *var_name, *var_value;
230
231                 /* this is pretty nasty, but works until we implement a proper
232                  * parser... */
233
234                 sep = strchr(value, '=');
235                 if (!sep)
236                         return;
237
238                 *sep = '\0';
239
240                 var_name = value;
241                 var_value = sep + 1;
242                 if (var_value[0] == '"' || var_value[0] == '\'')
243                         var_value++;
244
245                 if (!strlen(var_name) || !strlen(var_value))
246                         return;
247
248                 if (streq(var_name, "default"))
249                         state->default_idx = atoi(var_value);
250
251                 return;
252         }
253
254         pb_log("%s: unknown name: %s\n", __func__, name);
255 }
256
257 static const char *const grub2_conf_files[] = {
258         "/grub.cfg",
259         "/menu.lst",
260         "/grub/grub.cfg",
261         "/grub2/grub.cfg",
262         "/grub/menu.lst",
263         "/boot/grub/grub.cfg",
264         "/boot/grub2/grub.cfg",
265         "/boot/grub/menu.lst",
266         "/GRUB.CFG",
267         "/MENU.LST",
268         "/GRUB/GRUB.CFG",
269         "/GRUB2/GRUB.CFG",
270         "/GRUB/MENU.LST",
271         "/BOOT/GRUB/GRUB.CFG",
272         "/BOOT/GRUB/MENU.LST",
273         NULL
274 };
275
276 static const char *grub2_known_names[] = {
277         "menuentry",
278         "linux",
279         "linux16",
280         "initrd",
281         "search",
282         "set",
283         NULL
284 };
285
286 static int grub2_parse(struct discover_context *dc, char *buf, int len)
287 {
288         struct conf_context *conf;
289         struct grub2_state *state;
290
291         conf = talloc_zero(dc, struct conf_context);
292
293         if (!conf)
294                 return 0;
295
296         conf->dc = dc;
297         conf_init_global_options(conf);
298         conf->get_pair = conf_get_pair_space;
299         conf->process_pair = grub2_process_pair;
300         conf->finish = grub2_finish;
301         conf->parser_info = state = talloc_zero(conf, struct grub2_state);
302
303         state->known_names = grub2_known_names;
304         state->default_idx = -1;
305
306         conf_parse_buf(conf, buf, len);
307
308         talloc_free(conf);
309         return 1;
310 }
311
312 static struct parser grub2_parser = {
313         .name                   = "grub2",
314         .method                 = CONF_METHOD_LOCAL_FILE,
315         .parse                  = grub2_parse,
316         .filenames              = grub2_conf_files,
317         .resolve_resource       = resolve_grub2_resource,
318 };
319
320 register_parser(grub2_parser);