f2fac3e869ff6816701dcf03a6478a8bce4e19b5
[petitboot] / discover / kboot-parser.c
1 #define _GNU_SOURCE
2
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <unistd.h>
9
10 #include <fcntl.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13
14 #include <talloc/talloc.h>
15 #include <log/log.h>
16
17 #include "pb-protocol/pb-protocol.h"
18 #include "paths.h"
19 #include "params.h"
20 #include "parser-utils.h"
21 #include "device-handler.h"
22
23 #define buf_size 1024
24
25 struct kboot_context {
26         struct discover_context *discover;
27
28         char *buf;
29
30         struct global_option {
31                 char *name;
32                 char *value;
33         } *global_options;
34         int n_global_options;
35 };
36
37 static int param_is_ignored(const char *param)
38 {
39         static const char *ignored_options[] =
40                 { "message", "timeout", "default", NULL };
41         const char **str;
42
43         for (str = ignored_options; *str; str++)
44                 if (streq(*str, param))
45                         return 1;
46         return 0;
47 }
48
49 /**
50  * Splits a name=value pair, with value terminated by @term (or nul). if there
51  * is no '=', then only the value is populated, and *name is set to NULL. The
52  * string is modified in place.
53  *
54  * Returns the next byte to process, or null if we've hit the end of the
55  * string.
56  *
57  * */
58 static char *get_param_pair(char *str, char **name_out, char **value_out,
59                 char terminator)
60 {
61         char *sep, *tmp, *name, *value;
62
63         /* terminate the value */
64         tmp = strchr(str, terminator);
65         if (tmp)
66                 *tmp = 0;
67         else
68                 tmp = NULL;
69
70         sep = strchr(str, '=');
71         if (!sep) {
72                 *name_out = NULL;
73                 *value_out = str;
74                 return tmp ? tmp + 1 : NULL;
75         }
76
77         /* terminate the name */
78         *sep = 0;
79
80         /* remove leading spaces */
81         for (name = str; isspace(*name); name++);
82         for (value = sep + 1; isspace(*value); value++);
83
84         /* .. and trailing ones.. */
85         for (sep--; isspace(*sep); sep--)
86                 *sep = 0;
87         for (sep = value + strlen(value) - 1; isspace(*sep); sep--)
88                 *sep = 0;
89
90         *name_out = name;
91         *value_out = value;
92
93         return tmp ? tmp + 1 : NULL;
94 }
95
96 static struct global_option global_options[] = {
97         { .name = "root" },
98         { .name = "initrd" },
99         { .name = "video" },
100         { .name = NULL }
101 };
102
103 /*
104  * Check if an option (name=value) is a global option. If so, store it in
105  * the global options table, and return 1. Otherwise, return 0.
106  */
107 static int check_for_global_option(struct kboot_context *ctx,
108                 const char *name, const char *value)
109 {
110         int i;
111
112         for (i = 0; i < ctx->n_global_options; i++) {
113                 if (!strcmp(name, ctx->global_options[i].name)) {
114                         global_options[i].value = strdup(value);
115                         break;
116                 }
117         }
118         return 0;
119 }
120
121 static char *get_global_option(
122                 struct kboot_context *ctx __attribute__((unused)),
123                 const char *name)
124 {
125         int i;
126
127         for (i = 0; global_options[i].name ;i++)
128                 if (!strcmp(name, global_options[i].name))
129                         return global_options[i].value;
130
131         return NULL;
132 }
133
134 static int parse_option(struct kboot_context *kboot_ctx, char *opt_name,
135                 char *config)
136 {
137         char *pos, *name, *value, *root, *initrd, *cmdline, *tmp;
138         struct boot_option *opt;
139
140         root = initrd = cmdline = NULL;
141
142         /* remove quotes around the value */
143         while (*config == '"' || *config == '\'')
144                 config++;
145
146         pos = config + strlen(config) - 1;
147         while (*pos == '"' || *pos == '\'')
148                 *(pos--) = 0;
149
150         if (!strlen(pos))
151                 return 0;
152
153         pos = strchr(config, ' ');
154
155         opt = talloc_zero(kboot_ctx, struct boot_option);
156         opt->id = talloc_asprintf(opt, "%s#%s",
157                         kboot_ctx->discover->device->id, opt_name);
158         opt->name = talloc_strdup(opt, opt_name);
159
160         /* if there's no space, it's only a kernel image with no params */
161         if (!pos) {
162                 opt->boot_image_file = resolve_path(opt, config,
163                                 kboot_ctx->discover->device_path);
164                 opt->description = talloc_strdup(opt, config);
165                 goto out_add;
166         }
167
168         *pos = 0;
169         opt->boot_image_file = resolve_path(opt, config,
170                         kboot_ctx->discover->device_path);
171
172         cmdline = talloc_array(opt, char, buf_size);
173         *cmdline = 0;
174
175         for (pos++; pos;) {
176                 pos = get_param_pair(pos, &name, &value, ' ');
177
178                 if (!name) {
179                         strcat(cmdline, " ");
180                         strcat(cmdline, value);
181
182                 } else if (streq(name, "initrd")) {
183                         initrd = value;
184
185                 } else if (streq(name, "root")) {
186                         root = value;
187
188                 } else {
189                         strcat(cmdline, " ");
190                         *(value - 1) = '=';
191                         strcat(cmdline, name);
192                 }
193         }
194
195         if (!root)
196                 root = get_global_option(kboot_ctx, "root");
197         if (!initrd)
198                 initrd = get_global_option(kboot_ctx, "initrd");
199
200         if (initrd) {
201                 tmp = talloc_asprintf(opt, "initrd=%s %s", initrd, cmdline);
202                 talloc_free(cmdline);
203                 cmdline = tmp;
204
205                 opt->initrd_file = resolve_path(opt, initrd,
206                                 kboot_ctx->discover->device_path);
207         }
208
209         if (root) {
210                 tmp = talloc_asprintf(opt, "root=%s %s", root, cmdline);
211                 talloc_free(cmdline);
212                 cmdline = tmp;
213
214         } else if (initrd) {
215                 /* if there's an initrd but no root, fake up /dev/ram0 */
216                 tmp = talloc_asprintf(opt, "root=/dev/ram0 %s", cmdline);
217                 talloc_free(cmdline);
218                 cmdline = tmp;
219         }
220
221         opt->boot_args = cmdline;
222
223         opt->description = talloc_asprintf(opt, "%s %s",
224                         config, opt->boot_args);
225
226 out_add:
227         device_add_boot_option(kboot_ctx->discover->device, opt);
228         return 1;
229 }
230
231 static void parse_buf(struct kboot_context *kboot_ctx)
232 {
233         char *pos, *name, *value;
234
235         for (pos = kboot_ctx->buf; pos;) {
236                 pos = get_param_pair(pos, &name, &value, '\n');
237
238                 if (name == NULL || param_is_ignored(name))
239                         continue;
240
241                 if (*name == '#')
242                         continue;
243
244                 if (check_for_global_option(kboot_ctx, name, value))
245                         continue;
246
247                 parse_option(kboot_ctx, name, value);
248         }
249 }
250
251
252 static int kboot_parse(struct discover_context *ctx)
253 {
254         static const char *const conf_names[] = {
255                 "/etc/kboot.conf",
256                 "/etc/kboot.cnf",
257         };
258         struct kboot_context *kboot_ctx;
259         int fd, len, rc;
260         unsigned int i;
261         struct stat stat;
262
263         rc = 0;
264         fd = -1;
265
266         kboot_ctx = talloc_zero(ctx, struct kboot_context);
267         kboot_ctx->discover = ctx;
268
269         for (i = 0, len = 0; i < sizeof(conf_names) / sizeof(conf_names[0]);
270                 i++) {
271                 char *filepath = resolve_path(kboot_ctx, conf_names[i],
272                         ctx->device_path);
273
274                 pb_log("%s: try: %s\n", __func__, filepath);
275
276                 fd = open(filepath, O_RDONLY);
277                 if (fd < 0) {
278                         pb_log("%s: open failed: %s : %s\n", __func__, filepath,
279                                 strerror(errno));
280                         continue;
281                 }
282                 if (fstat(fd, &stat)) {
283                         pb_log("%s: fstat failed: %s : %s\n", __func__,
284                                 filepath, strerror(errno));
285                         continue;
286                 }
287
288                 kboot_ctx->buf = talloc_array(kboot_ctx, char,
289                         stat.st_size + 1);
290
291                 len = read(fd, kboot_ctx->buf, stat.st_size);
292                 if (len < 0) {
293                         pb_log("%s: read failed: %s : %s\n", __func__, filepath,
294                                 strerror(errno));
295                         continue;
296                 }
297                 kboot_ctx->buf[len] = 0;
298         }
299
300         if (len <= 0)
301                 goto out;
302
303         if (!ctx->device->icon_file)
304                 ctx->device->icon_file = talloc_strdup(ctx,
305                                 generic_icon_file(guess_device_type(ctx)));
306
307         parse_buf(kboot_ctx);
308
309         rc = 1;
310
311 out:
312         pb_log("%s: %s\n", __func__, (rc ? "ok" : "failed"));
313
314         if (fd >= 0)
315                 close(fd);
316         talloc_free(kboot_ctx);
317         return rc;
318 }
319
320 define_parser(kboot, 98, kboot_parse);