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