1/* Parse a Hagfish configuration file - which in turn is roughly a GRUB
2 * menu.lst.  */
3
4#include <assert.h>
5#include <ctype.h>
6#include <errno.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include "config.h"
12
13static inline int
14iscomment(char c) {
15    return c == '#';
16}
17
18static inline int
19istoken(char c) {
20    return !isspace(c) && !iscomment(c);
21}
22
23static size_t
24skip_whitespace(const char *buf, size_t size, size_t start, int skip_newlines) {
25    assert(start < size);
26    size_t i;
27
28    for(i= start;
29        i < size && ((isspace(buf[i]) && buf[i] != '\n') ||
30                     (skip_newlines && buf[i] == '\n'));
31        i++);
32
33    assert(start <= i);
34    assert(i <= size);
35    assert(i == size ||
36           !isspace(buf[i]) ||
37           (!skip_newlines && buf[i] == '\n'));
38    return i;
39}
40
41static size_t
42find_eol(const char *buf, size_t size, size_t start) {
43    assert(start < size);
44    size_t i;
45
46    for(i= start; i < size && buf[i] != '\n'; i++);
47
48    assert(start <= i);
49    assert(i <= size);
50    assert(i == size || buf[i] == '\n');
51    return i;
52}
53
54static size_t
55find_token(const char *buf, size_t size, size_t start, int skip_newlines) {
56    assert(start < size);
57    size_t i= start;
58
59    while(i < size && !istoken(buf[i])) {
60        if(isspace(buf[i])) {
61            /* Skip whitespace. */
62            i= skip_whitespace(buf, size, i, skip_newlines);
63        }
64        else {
65            /* Find the newline. */
66            i= find_eol(buf, size, i);
67            /* Skip over it, if not at EOF. */
68            if(i < size) i++;
69        }
70    }
71
72    assert(start <= i);
73    assert(i <= size);
74    assert(i == size || istoken(buf[i]));
75    return i;
76}
77
78static size_t
79get_token(const char *buf, size_t size, size_t start) {
80    assert(start < size);
81    assert(istoken(buf[start]));
82    size_t i;
83
84    for(i= start; i < size && istoken(buf[i]); i++);
85
86    assert(start < i);
87    assert(i <= size);
88    assert(istoken(buf[i-1]));
89    return i;
90}
91
92static int
93get_cmdline(const char *buf, size_t size, size_t *cursor,
94            size_t *cstart, size_t *clen, size_t *astart, size_t *alen) {
95    assert(*cursor < size);
96    *cursor= find_token(buf, size, *cursor, 0);
97    if(!istoken(buf[*cursor])) {
98        fprintf(stderr, "Missing command line\n");
99        return 0;
100    }
101    *astart= *cstart= *cursor; /* Path starts here. */
102    *cursor= get_token(buf, size, *cursor);
103    *clen= *cursor - *cstart; /* Path ends here. */
104    assert(*clen <= size - *cstart);
105    *cursor= find_eol(buf, size, *cursor);
106    *alen= *cursor - *astart;
107    assert(*alen <= size - *astart); /* Arguments end here. */
108
109    return 1;
110}
111
112struct config *
113parse_config(char *buf, size_t size) {
114    size_t cursor= 0;
115    struct config *cfg;
116
117    cfg= calloc(1, sizeof(struct config));
118    if(!cfg) {
119        fprintf(stderr, "calloc: %s\n", strerror(errno));
120        goto parse_fail;
121    }
122    cfg->buf= buf;
123    cfg->stack_size= DEFAULT_STACK_SIZE;
124
125    while(cursor < size) {
126        cursor= find_token(buf, size, cursor, 1);
127        if(cursor < size) {
128            size_t tstart= cursor, tlen;
129
130            assert(istoken(buf[cursor]));
131            cursor= get_token(buf, size, cursor);
132            tlen= cursor - tstart;
133            assert(tlen <= size - cursor);
134
135            if(!strncmp("title", buf+tstart, 5)) {
136                /* Ignore the title. */
137                assert(cursor < size);
138                cursor= find_eol(buf, size, cursor);
139            }
140            else if(!strncmp("stack", buf+tstart, 5)) {
141                char arg[10];
142                size_t astart, alen;
143
144                cursor= skip_whitespace(buf, size, cursor, 0);
145                if(!istoken(buf[cursor])) {
146                    fprintf(stderr, "Expected stack size\n");
147                    goto parse_fail;
148                }
149                astart= cursor;
150
151                cursor= get_token(buf, size, cursor);
152                alen= cursor - astart;
153                assert(alen <= size - cursor);
154
155                if(alen > 9) {
156                    fprintf(stderr, "Stack size field too long\n");
157                    goto parse_fail;
158                }
159
160                memcpy(arg, buf+astart, alen);
161                arg[alen]= '\0';
162                cfg->stack_size= strtoul(arg, NULL, 10);
163            }
164            else if(!strncmp("kernel", buf+tstart, 6)) {
165                if(cfg->kernel) {
166                    fprintf(stderr, "Kernel defined twice\n");
167                    goto parse_fail;
168                }
169
170                cfg->kernel= calloc(1, sizeof(struct component_config));
171                if(!cfg->kernel) {
172                    fprintf(stderr, "calloc: %s\n", strerror(errno));
173                    goto parse_fail;
174                }
175
176                /* Grab the command line. */
177                if(!get_cmdline(buf, size, &cursor,
178                                &cfg->kernel->path_start,
179                                &cfg->kernel->path_len,
180                                &cfg->kernel->args_start,
181                                &cfg->kernel->args_len))
182                    goto parse_fail;
183            }
184            else if(!strncmp("module", buf+tstart, 6)) {
185                struct component_config *module=
186                    calloc(1, sizeof(struct component_config));
187                if(!module) {
188                    fprintf(stderr, "calloc, %s\n", strerror(errno));
189                    goto parse_fail;
190                }
191
192                /* Grab the command line. */
193                if(!get_cmdline(buf, size, &cursor,
194                                &module->path_start,
195                                &module->path_len,
196                                &module->args_start,
197                                &module->args_len))
198                    goto parse_fail;
199
200                if(cfg->first_module) {
201                    assert(cfg->last_module);
202                    cfg->last_module->next= module;
203                    cfg->last_module= module;
204                }
205                else {
206                    assert(!cfg->last_module);
207                    cfg->first_module= module;
208                    cfg->last_module= module;
209                }
210            }
211            else {
212                fprintf(stderr,
213                           "Unrecognised entry \"%.*s\", skipping line.\n",
214                           (int)tlen, buf + tstart);
215                cursor= find_eol(buf, size, cursor);
216            }
217        }
218    }
219
220    if(!cfg->kernel) {
221        fprintf(stderr, "No kernel image specified\n");
222        goto parse_fail;
223    }
224
225    return cfg;
226
227parse_fail:
228    if(cfg) {
229        if(cfg->kernel) free(cfg->kernel);
230
231        struct component_config *cmp= cfg->first_module;
232        while(cmp) {
233            struct component_config *next= cmp->next;
234            free(cmp);
235            cmp= next;
236        }
237
238        free(cfg);
239    }
240    return NULL;
241}
242