More robusst comment-ignoring in kboot parser.
[petitboot] / devices / 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 *mountpoint;
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 static int parse_option(struct boot_option *opt, char *config)
80 {
81         char *pos, *name, *value, *root, *initrd, *cmdline, *tmp;
82
83         root = initrd = cmdline = NULL;
84
85         /* remove quotes around the value */
86         while (*config == '"' || *config == '\'')
87                 config++;
88
89         pos = config + strlen(config) - 1;
90         while (*pos == '"' || *pos == '\'')
91                 *(pos--) = 0;
92
93         if (!strlen(pos))
94                 return 0;
95
96         pos = strchr(config, ' ');
97
98         /* if there's no space, it's only a kernel image with no params */
99         if (!pos) {
100                 opt->boot_image_file = join_paths(mountpoint, config);
101                 opt->description = strdup(config);
102                 return 1;
103         }
104
105         *pos = 0;
106         opt->boot_image_file = join_paths(mountpoint, config);
107
108         cmdline = malloc(buf_size);
109         *cmdline = 0;
110
111         for (pos++; pos;) {
112                 pos = get_param_pair(pos, &name, &value, ' ');
113
114                 if (!name) {
115                         strcat(cmdline, " ");
116                         strcat(cmdline, value);
117
118                 } else if (streq(name, "initrd")) {
119                         initrd = value;
120
121                 } else if (streq(name, "root")) {
122                         root = value;
123
124                 } else {
125                         strcat(cmdline, " ");
126                         *(value - 1) = '=';
127                         strcat(cmdline, name);
128                 }
129         }
130
131         if (initrd) {
132                 asprintf(&tmp, "initrd=%s %s", initrd, cmdline);
133                 free(cmdline);
134                 cmdline = tmp;
135
136                 opt->initrd_file = join_paths(mountpoint, initrd);
137         }
138
139         if (root) {
140                 asprintf(&tmp, "root=%s %s", root, cmdline);
141                 free(cmdline);
142                 cmdline = tmp;
143
144         } else if (!initrd) {
145                 /* if there's an initrd but no root, fake up /dev/ram0 */
146                 asprintf(&tmp, "root=/dev/ram0 %s", cmdline);
147                 free(cmdline);
148                 cmdline = tmp;
149         }
150
151         pb_log("kboot cmdline: %s", cmdline);
152         opt->boot_args = cmdline;
153
154         asprintf(&opt->description, "%s %s", config, cmdline);
155
156         return 1;
157 }
158
159 static void parse_buf(struct device *dev, char *buf)
160 {
161         char *pos, *name, *value;
162         int sent_device = 0;
163
164         for (pos = buf; pos;) {
165                 struct boot_option opt;
166
167                 pos = get_param_pair(pos, &name, &value, '\n');
168
169                 pb_log("kboot param: '%s' = '%s'\n", name, value);
170
171                 if (name == NULL || param_is_ignored(name))
172                         continue;
173
174                 if (*name == '#')
175                         continue;
176
177                 memset(&opt, 0, sizeof(opt));
178                 opt.name = strdup(name);
179
180                 if (parse_option(&opt, value))
181                         if (!sent_device++)
182                                 add_device(dev);
183                         add_boot_option(&opt);
184
185                 free(opt.name);
186         }
187 }
188
189 static int parse(const char *devicepath, const char *_mountpoint)
190 {
191         char *filepath, *buf;
192         int fd, len, rc = 0;
193         struct stat stat;
194         struct device *dev;
195
196         mountpoint = _mountpoint;
197
198         filepath = join_paths(mountpoint, "/etc/kboot.conf");
199
200         fd = open(filepath, O_RDONLY);
201         if (fd < 0)
202                 goto out_free_path;
203
204         if (fstat(fd, &stat))
205                 goto out_close;
206
207         buf = malloc(stat.st_size + 1);
208         if (!buf)
209                 goto out_close;;
210
211         len = read(fd, buf, stat.st_size);
212         if (len < 0)
213                 goto out_free_buf;
214         buf[len] = 0;
215
216         dev = malloc(sizeof(*dev));
217         memset(dev, 0, sizeof(*dev));
218         dev->id = strdup(devicepath);
219         dev->icon_file = strdup(generic_icon_file(guess_device_type()));
220
221         parse_buf(dev, buf);
222
223         rc = 1;
224
225 out_free_buf:
226         free(buf);
227 out_close:
228         close(fd);
229 out_free_path:
230         free(filepath);
231         return rc;
232 }
233
234 struct parser kboot_parser = {
235         .name = "kboot.conf parser",
236         .priority = 98,
237         .parse    = parse
238 };