blob: b0c396d68350ac8717d30452aa460fba19b91d81 [file] [log] [blame]
Peer Chen8d782ee2011-01-18 21:34:18 -05001/**
2 * Copyright (c) 2011 NVIDIA Corporation. All rights reserved.
3 *
4 * See file CREDITS for list of people who contributed to this
5 * project.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
20 * MA 02111-1307 USA
21 */
22
23/*
24 * parse.c - Parsing support for the cbootimage tool
25 */
26
27/*
28 * TODO / Notes
29 * - Add doxygen commentary
30 * - Do we have endian issues to deal with?
31 * - Add support for device configuration data
32 * - Add support for bad blocks
33 * - Add support for different allocation modes/strategies
34 * - Add support for multiple BCTs in journal block
35 * - Add support for other missing features.
36 */
37
38#include "parse.h"
39#include "cbootimage.h"
40#include "data_layout.h"
41#include "crypto.h"
42#include "set.h"
43
44/*
45 * Function prototypes
46 *
47 * ParseXXX() parses XXX in the input
48 * SetXXX() sets state based on the parsing results but does not perform
49 * any parsing of its own
50 * A ParseXXX() function may call other parse functions and set functions.
51 * A SetXXX() function may not call any parseing functions.
52 */
53
54static char *parse_u32(char *statement, u_int32_t *val);
Peer Chen7557d9b2011-02-24 09:29:23 -080055static char *parse_u8(char *statement, u_int32_t *val);
Peer Chen8d782ee2011-01-18 21:34:18 -050056static char *parse_filename(char *statement, char *name, int chars_remaining);
Peer Chen7557d9b2011-02-24 09:29:23 -080057static char *parse_enum(build_image_context *context,
58 char *statement,
59 enum_item *table,
60 u_int32_t *val);
61static char
62*parse_field_name(char *rest, field_item *field_table, field_item **field);
63static char
64*parse_field_value(build_image_context *context,
65 char *rest,
66 field_item *field,
67 u_int32_t *value);
Peer Chen8d782ee2011-01-18 21:34:18 -050068static int
69parse_array(build_image_context *context, parse_token token, char *rest);
70static int
71parse_bootloader(build_image_context *context, parse_token token, char *rest);
72static int
73parse_value_u32(build_image_context *context, parse_token token, char *rest);
74static int
75parse_bct_file(build_image_context *context, parse_token token, char *rest);
76static int
77parse_addon(build_image_context *context, parse_token token, char *rest);
78static char *parse_string(char *statement, char *uname, int chars_remaining);
79static char
80*parse_end_state(char *statement, char *uname, int chars_remaining);
Peer Chen7557d9b2011-02-24 09:29:23 -080081static int
82parse_dev_param(build_image_context *context, parse_token token, char *rest);
83
Peer Chen8d782ee2011-01-18 21:34:18 -050084static int process_statement(build_image_context *context, char *statement);
85
Peer Chen7557d9b2011-02-24 09:29:23 -080086static enum_item s_devtype_table[] =
87{
88 { "NvBootDevType_Sdmmc", nvbct_lib_id_dev_type_sdmmc },
89 { "NvBootDevType_Spi", nvbct_lib_id_dev_type_spi },
90 { "Sdmmc", nvbct_lib_id_dev_type_sdmmc },
91 { "Spi", nvbct_lib_id_dev_type_spi },
92
93 { NULL, 0 }
94};
95
96static enum_item s_sdmmc_data_width_table[] =
97{
98 {
99 "NvBootSdmmcDataWidth_4Bit",
100 nvbct_lib_id_sdmmc_data_width_4bit
101 },
102 {
103 "NvBootSdmmcDataWidth_8Bit",
104 nvbct_lib_id_sdmmc_data_width_8bit
105 },
106 { "4Bit", nvbct_lib_id_sdmmc_data_width_4bit },
107 { "8Bit", nvbct_lib_id_sdmmc_data_width_8bit },
108 { NULL, 0 }
109};
110
111static enum_item s_spi_clock_source_table[] =
112{
113 {
114 "NvBootSpiClockSource_PllPOut0",
115 nvbct_lib_id_spi_clock_source_pllp_out0
116 },
117 {
118 "NvBootSpiClockSource_PllCOut0",
119 nvbct_lib_id_spi_clock_source_pllc_out0
120 },
121 {
122 "NvBootSpiClockSource_PllMOut0",
123 nvbct_lib_id_spi_clock_source_pllm_out0
124 },
125 {
126 "NvBootSpiClockSource_ClockM",
127 nvbct_lib_id_spi_clock_source_clockm
128 },
129
130 { "ClockSource_PllPOut0", nvbct_lib_id_spi_clock_source_pllp_out0 },
131 { "ClockSource_PllCOut0", nvbct_lib_id_spi_clock_source_pllc_out0 },
132 { "ClockSource_PllMOut0", nvbct_lib_id_spi_clock_source_pllm_out0 },
133 { "ClockSource_ClockM", nvbct_lib_id_spi_clock_source_clockm },
134
135
136 { "PllPOut0", nvbct_lib_id_spi_clock_source_pllp_out0 },
137 { "PllCOut0", nvbct_lib_id_spi_clock_source_pllc_out0 },
138 { "PllMOut0", nvbct_lib_id_spi_clock_source_pllm_out0 },
139 { "ClockM", nvbct_lib_id_spi_clock_source_clockm },
140
141 { NULL, 0 }
142};
143
144static field_item s_sdmmc_table[] =
145{
146 { "ClockDivider", token_clock_divider, field_type_u32, NULL },
147 { "DataWidth", token_data_width,
148 field_type_enum, s_sdmmc_data_width_table },
149 { "MaxPowerClassSupported", token_max_power_class_supported,
150 field_type_u32, NULL },
151
152 { NULL, 0, 0, NULL }
153};
154
155static field_item s_spiflash_table[] =
156{
157 { "ReadCommandTypeFast", token_read_command_type_fast,
158 field_type_u8, NULL },
159 { "ClockDivider", token_clock_divider, field_type_u8, NULL },
160 { "ClockSource", token_clock_source,
161 field_type_enum, s_spi_clock_source_table },
162
163 { NULL, 0, 0, NULL }
164};
165
166static parse_subfield_item s_device_type_table[] =
167{
168 { "SdmmcParams.", token_sdmmc_params,
169 s_sdmmc_table, set_sdmmc_param },
170 { "SpiFlashParams.", token_spiflash_params,
171 s_spiflash_table, set_spiflash_param },
172
173 { NULL, 0, NULL }
174};
175
Peer Chen8d782ee2011-01-18 21:34:18 -0500176static parse_item s_top_level_items[] =
177{
178 { "Bctfile=", token_bct_file, parse_bct_file },
179 { "Attribute=", token_attribute, parse_value_u32 },
180 { "Attribute[", token_attribute, parse_array },
Peer Chen7557d9b2011-02-24 09:29:23 -0800181 { "PageSize=", token_page_size, parse_value_u32 },
182 { "BlockSize=", token_block_size, parse_value_u32 },
183 { "PartitionSize=", token_partition_size, parse_value_u32 },
184 { "DevType[", token_dev_type, parse_array },
185 { "DeviceParam[", token_dev_param, parse_dev_param },
Peer Chen8d782ee2011-01-18 21:34:18 -0500186 { "BootLoader=", token_bootloader, parse_bootloader },
187 { "Redundancy=", token_redundancy, parse_value_u32 },
188 { "Version=", token_version, parse_value_u32 },
189 { "AddOn[", token_addon, parse_addon },
190 { NULL, 0, NULL } /* Must be last */
191};
192
193/* Macro to simplify parser code a bit. */
194#define PARSE_COMMA(x) if (*rest != ',') return (x); rest++
195
196/* This parsing code was initially borrowed from nvcamera_config_parse.c. */
197/* Returns the address of the character after the parsed data. */
198static char *
199parse_u32(char *statement, u_int32_t *val)
200{
201 u_int32_t value = 0;
202
203 while (*statement=='0') {
204 statement++;
205 }
206
207 if (*statement=='x' || *statement=='X') {
208 statement++;
209 while (((*statement >= '0') && (*statement <= '9')) ||
210 ((*statement >= 'a') && (*statement <= 'f')) ||
211 ((*statement >= 'A') && (*statement <= 'F'))) {
212 value *= 16;
213 if ((*statement >= '0') && (*statement <= '9')) {
214 value += (*statement - '0');
215 } else if ((*statement >= 'A') &&
216 (*statement <= 'F')) {
217 value += ((*statement - 'A')+10);
218 } else {
219 value += ((*statement - 'a')+10);
220 }
221 statement++;
222 }
223 } else {
224 while (*statement >= '0' && *statement <= '9') {
225 value = value*10 + (*statement - '0');
226 statement++;
227 }
228 }
229 *val = value;
230 return statement;
231}
232
Peer Chen7557d9b2011-02-24 09:29:23 -0800233char *
234parse_u8(char *statement, u_int32_t *val)
235{
236 char *retval;
237
238 retval = parse_u32(statement, val);
239
240 if (*val > 0xff) {
241 printf("Warning: Parsed 8-bit value that exceeded 8-bits.\n");
242 printf(" Parsed value = %d. Remaining text = %s\n",
243 *val, retval);
244 }
245
246 return retval;
247}
248
249
Peer Chen8d782ee2011-01-18 21:34:18 -0500250/* This parsing code was initially borrowed from nvcamera_config_parse.c. */
251/* Returns the address of the character after the parsed data. */
252static char *
253parse_filename(char *statement, char *name, int chars_remaining)
254{
255 while (((*statement >= '0') && (*statement <= '9')) ||
256 ((*statement >= 'a') && (*statement <= 'z')) ||
257 ((*statement >= 'A') && (*statement <= 'Z')) ||
258 (*statement == '\\') ||
259 (*statement == '/' ) ||
260 (*statement == '~' ) ||
261 (*statement == '_' ) ||
262 (*statement == '-' ) ||
263 (*statement == '+' ) ||
264 (*statement == ':' ) ||
265 (*statement == '.' )) {
266 /* Check if the filename buffer is out of space, preserving one
267 * character to null terminate the string.
268 */
269 chars_remaining--;
270
271 if (chars_remaining < 1)
272 return NULL;
273 *name++ = *statement++;
274 }
275
276 /* Null terminate the filename. */
277 *name = '\0';
278
279 return statement;
280}
281
Peer Chen7557d9b2011-02-24 09:29:23 -0800282static char
283*parse_field_name(char *rest, field_item *field_table, field_item **field)
284{
285 u_int32_t i;
286 u_int32_t field_name_len = 0;
287
288 assert(field_table != NULL);
289 assert(rest != NULL);
290 assert(field != NULL);
291
292 while(*(rest + field_name_len) != '=')
293 field_name_len++;
294
295 /* Parse the field name. */
296 for (i = 0; field_table[i].name != NULL; i++) {
297 if ((strlen(field_table[i].name) == field_name_len) &&
298 !strncmp(field_table[i].name,
299 rest,
300 field_name_len)) {
301
302 *field = &(field_table[i]);
303 rest = rest + field_name_len;
304 return rest;
305 }
306 }
307
308 /* Field wasn't found or a parse error occurred. */
309 return NULL;
310}
311
312static char
313*parse_field_value(build_image_context *context,
314 char *rest,
315 field_item *field,
316 u_int32_t *value)
317{
318 assert(rest != NULL);
319 assert(field != NULL);
320 assert((field->type != field_type_enum)
321 || (field->enum_table != NULL));
322
323 switch (field->type) {
324 case field_type_enum:
325 rest = parse_enum(context, rest, field->enum_table, value);
326 break;
327
328 case field_type_u32:
329 rest = parse_u32(rest, value);
330 break;
331
332 case field_type_u8:
333 rest = parse_u8(rest, value);
334 break;
335
336 default:
337 printf("Unexpected field type %d at line %d\n",
338 field->type, __LINE__);
339 rest = NULL;
340 break;
341 }
342
343 return rest;
344}
345
346static char *
347parse_enum(build_image_context *context,
348 char *statement,
349 enum_item *table,
350 u_int32_t *val)
351{
352 int i;
353 char *rest;
354 int e;
355
356 for (i = 0; table[i].name != NULL; i++) {
357 if (!strncmp(table[i].name, statement,
358 strlen(table[i].name))) {
359 /* Lookup the correct value for the token. */
360 e = context->bctlib.get_value(table[i].value,
361 val, context->bct);
362 if (e) {
363 printf("Error looking up token %d.\n", table[i].value);
364 printf("\"%s\" is not valid for this chip.\n",
365 table[i].name);
366 *val = -1;
367 }
368
369 rest = statement + strlen(table[i].name);
370 return rest;
371 }
372 }
373 return parse_u32(statement, val);
374
375}
Peer Chen8d782ee2011-01-18 21:34:18 -0500376/*
377 * parse_bootloader(): Processes commands to set a bootloader.
378 */
379static int parse_bootloader(build_image_context *context,
380 parse_token token,
381 char *rest)
382{
383 char filename[MAX_BUFFER];
384 char e_state[MAX_STR_LEN];
385 u_int32_t load_addr;
386 u_int32_t entry_point;
387
388 assert(context != NULL);
389 assert(rest != NULL);
390
Peer Chen7557d9b2011-02-24 09:29:23 -0800391 if (context->generate_bct != 0)
392 return 0;
Peer Chen8d782ee2011-01-18 21:34:18 -0500393 /* Parse the file name. */
394 rest = parse_filename(rest, filename, MAX_BUFFER);
395 if (rest == NULL)
396 return 1;
397
398 PARSE_COMMA(1);
399
400 /* Parse the load address. */
401 rest = parse_u32(rest, &load_addr);
402 if (rest == NULL)
403 return 1;
404
405 PARSE_COMMA(1);
406
407 /* Parse the entry point. */
408 rest = parse_u32(rest, &entry_point);
409 if (rest == NULL)
410 return 1;
411
412 PARSE_COMMA(1);
413
414 /* Parse the end state. */
415 rest = parse_end_state(rest, e_state, MAX_STR_LEN);
416 if (rest == NULL)
417 return 1;
418 if (strncmp(e_state, "Complete", strlen("Complete")))
419 return 1;
420
421 /* Parsing has finished - set the bootloader */
422 return set_bootloader(context, filename, load_addr, entry_point);
423}
424
425/*
426 * parse_array(): Processes commands to set an array value.
427 */
428static int
429parse_array(build_image_context *context, parse_token token, char *rest)
430{
431 u_int32_t index;
432 u_int32_t value;
433
434 assert(context != NULL);
435 assert(rest != NULL);
436
437 /* Parse the index. */
438 rest = parse_u32(rest, &index);
439 if (rest == NULL)
440 return 1;
441
442 /* Parse the closing bracket. */
443 if (*rest != ']')
444 return 1;
445 rest++;
446
447 /* Parse the equals sign.*/
448 if (*rest != '=')
449 return 1;
450 rest++;
451
452 /* Parse the value based on the field table. */
453 switch(token) {
Peer Chen7557d9b2011-02-24 09:29:23 -0800454 case token_attribute:
455 rest = parse_u32(rest, &value);
456 break;
457 case token_dev_type:
458 rest = parse_enum(context, rest, s_devtype_table, &value);
459 break;
Peer Chen8d782ee2011-01-18 21:34:18 -0500460
Peer Chen7557d9b2011-02-24 09:29:23 -0800461 default:
462 /* Unknown token */
463 return 1;
Peer Chen8d782ee2011-01-18 21:34:18 -0500464 }
465
466 if (rest == NULL)
467 return 1;
468
469 /* Store the result. */
470 return context_set_array(context, index, token, value);
471}
472
473/*
474 * parse_value_u32(): General handler for setting u_int32_t values in config files.
475 */
476static int parse_value_u32(build_image_context *context,
477 parse_token token,
478 char *rest)
479{
480 u_int32_t value;
481
482 assert(context != NULL);
483 assert(rest != NULL);
484
485 rest = parse_u32(rest, &value);
486 if (rest == NULL)
487 return 1;
488
489 return context_set_value(context, token, value);
490}
491
492static int
493parse_bct_file(build_image_context *context, parse_token token, char *rest)
494{
495 char filename[MAX_BUFFER];
496
497 assert(context != NULL);
498 assert(rest != NULL);
499
500 /* Parse the file name. */
501 rest = parse_filename(rest, filename, MAX_BUFFER);
502 if (rest == NULL)
503 return 1;
504
505 /* Parsing has finished - set the bctfile */
506 context->bct_filename = filename;
507 /* Read the bct file to buffer */
508 read_bct_file(context);
509 return 0;
510}
511
512static char *
513parse_string(char *statement, char *uname, int chars_remaining)
514{
515 memset(uname, 0, chars_remaining);
516 while (((*statement >= '0') && (*statement <= '9')) ||
517 ((*statement >= 'A') && (*statement <= 'Z')) ||
518 ((*statement >= 'a') && (*statement <= 'z'))) {
519
520 *uname++ = *statement++;
521 if (--chars_remaining < 0) {
522 printf("String length beyond the boundary!!!");
523 return NULL;
524 }
525 }
526 *uname = '\0';
527 return statement;
528}
529
530static char *
531parse_end_state(char *statement, char *uname, int chars_remaining)
532{
533 while (((*statement >= 'a') && (*statement <= 'z')) ||
534 ((*statement >= 'A') && (*statement <= 'Z'))) {
535
536 *uname++ = *statement++;
537 if (--chars_remaining < 0)
538 return NULL;
539 }
540 *uname = '\0';
541 return statement;
542}
543
544
545/* Parse the addon component */
546static int
547parse_addon(build_image_context *context, parse_token token, char *rest)
548{
549 char filename[MAX_BUFFER];
550 char u_name[4];
551 char e_state[MAX_STR_LEN];
552 u_int32_t index;
553 u_int32_t item_attr;
554 u_int32_t others;
555 char other_str[MAX_STR_LEN];
556
557 assert(context != NULL);
558 assert(rest != NULL);
559
560 /* Parse the index. */
561 rest = parse_u32(rest, &index);
562 if (rest == NULL)
563 return 1;
564
565 /* Parse the closing bracket. */
566 if (*rest != ']')
567 return 1;
568 rest++;
569
570 /* Parse the equals sign.*/
571 if (*rest != '=')
572 return 1;
573 rest++;
574
575 rest = parse_filename(rest, filename, MAX_BUFFER);
576 if (rest == NULL)
577 return 1;
578 if (set_addon_filename(context, filename, index) != 0)
579 return 1;
580
581 PARSE_COMMA(1);
582
583 rest = parse_string(rest, u_name, 3);
584 if (rest == NULL) {
585 printf("Unique name should be 3 characters.\n");
586 return 1;
587 }
588 if (set_unique_name(context, u_name, index) != 0)
589 return 1;
590
591 PARSE_COMMA(1);
592
593 rest = parse_u32(rest, &item_attr);
594 if (rest == NULL)
595 return 1;
596 if (set_addon_attr(context, item_attr, index) != 0)
597 return 1;
598
599 PARSE_COMMA(1);
600
601 if (*rest == '0' && (*(rest + 1) == 'x' ||*(rest + 1) == 'X')) {
602 rest = parse_u32(rest, &others);
603 if (set_other_field(context, NULL, others, index) != 0)
604 return 1;
605 } else {
606 rest = parse_string(rest, other_str, 16);
607 if (set_other_field(context, other_str, 0, index) != 0)
608 return 1;
609 }
610 if (rest == NULL)
611 return 1;
612
613 PARSE_COMMA(1);
614
615 rest = parse_end_state(rest, e_state, MAX_STR_LEN);
616 if (rest == NULL)
617 return 1;
618 if (strncmp(e_state, "Complete", strlen("Complete")))
619 return 1;
620 return 0;
621}
622
Peer Chen7557d9b2011-02-24 09:29:23 -0800623static int
624parse_dev_param(build_image_context *context, parse_token token, char *rest)
625{
626 u_int32_t i;
627 u_int32_t value;
628 field_item *field;
629 u_int32_t index;
630 parse_subfield_item *device_item = NULL;
631
632 assert(context != NULL);
633 assert(rest != NULL);
634
635 /* Parse the index. */
636 rest = parse_u32(rest, &index);
637 if (rest == NULL)
638 return 1;
639
640 /* Parse the closing bracket. */
641 if (*rest != ']')
642 return 1;
643 rest++;
644
645 /* Parse the following '.' */
646 if (*rest != '.')
647 return 1;
648 rest++;
649
650 /* Parse the device name. */
651 for (i = 0; s_device_type_table[i].prefix != NULL; i++) {
652 if (!strncmp(s_device_type_table[i].prefix,
653 rest, strlen(s_device_type_table[i].prefix))) {
654
655 device_item = &(s_device_type_table[i]);
656 rest = rest + strlen(s_device_type_table[i].prefix);
657
658 /* Parse the field name. */
659 rest = parse_field_name(rest,
660 s_device_type_table[i].field_table,
661 &field);
662 if (rest == NULL)
663 return 1;
664
665 /* Parse the equals sign.*/
666 if (*rest != '=')
667 return 1;
668 rest++;
669
670 /* Parse the value based on the field table. */
671 rest = parse_field_value(context, rest, field, &value);
672 if (rest == NULL)
673 return 1;
674 return device_item->process(context,
675 index, field->token, value);
676 }
677 }
678
679 return 1;
680
681}
Peer Chen8d782ee2011-01-18 21:34:18 -0500682/* Return 0 on success, 1 on error */
683static int
684process_statement(build_image_context *context, char *statement)
685{
686 int i;
687 char *rest;
688
689 for (i = 0; s_top_level_items[i].prefix != NULL; i++) {
690 if (!strncmp(s_top_level_items[i].prefix, statement,
691 strlen(s_top_level_items[i].prefix))) {
692 rest = statement + strlen(s_top_level_items[i].prefix);
693
694 return s_top_level_items[i].process(context,
695 s_top_level_items[i].token,
696 rest);
697 }
698 }
699
700 /* If this point was reached, there was a processing error. */
701 return 1;
702}
703
704/* Note: Basic parsing borrowed from nvcamera_config.c */
705void process_config_file(build_image_context *context)
706{
707 char buffer[MAX_BUFFER];
708 int space = 0;
709 char current;
710 u_int8_t c_eol_comment_start = 0; // True after first slash
711 u_int8_t comment = 0;
712 u_int8_t string = 0;
713 u_int8_t equal_encounter = 0;
714
715 assert(context != NULL);
716 assert(context->config_file != NULL);
717
718 while ((current = fgetc(context->config_file)) !=EOF) {
719 if (space >= (MAX_BUFFER-1)) {
720 /* if we exceeded the max buffer size, it is likely
721 due to a missing semi-colon at the end of a line */
722 printf("Config file parsing error!");
723 exit(1);
724 }
725
726 /* Handle failure to complete "//" comment token.
727 Insert the '/' into the busffer and proceed with
728 processing the current character. */
729 if (c_eol_comment_start && current != '/') {
730 c_eol_comment_start = 0;
731 buffer[space++] = '/';
732 }
733
734 switch (current) {
735 case '\"': /* " indicates start or end of a string */
736 if (!comment) {
737 string ^= 1;
738 buffer[space++] = current;
739 }
740 break;
741 case ';':
742 if (!string && !comment) {
743 buffer[space++] = '\0';
744
745 /* Process a statement. */
746 if (process_statement(context, buffer)) {
747 goto error;
748 }
749 space = 0;
750 equal_encounter = 0;
751 } else if (string) {
752 buffer[space++] = current;
753 }
754 break;
755
756 case '/':
757 if (!string && !comment) {
758 if (c_eol_comment_start) {
759 /* EOL comment started. */
760 comment = 1;
761 c_eol_comment_start = 0;
762 } else {
763 /* Potential start of eol comment. */
764 c_eol_comment_start = 1;
765 }
766 } else if (!comment) {
767 buffer[space++] = current;
768 }
769 break;
770
771 /* ignore whitespace. uses fallthrough */
772 case '\n':
773 case '\r': /* carriage returns end comments */
774 string = 0;
775 comment = 0;
776 c_eol_comment_start = 0;
777 case ' ':
778 case '\t':
779 if (string) {
780 buffer[space++] = current;
781 }
782 break;
783
784 case '#':
785 if (!string) {
786 comment = 1;
787 } else {
788 buffer[space++] = current;
789 }
790 break;
791
792 default:
793 if (!comment) {
794 buffer[space++] = current;
795 if (current == '=') {
796 if (!equal_encounter) {
797 equal_encounter = 1;
798 } else {
799 goto error;
800 }
801 }
802 }
803 break;
804 }
805 }
806
807 return;
808
809 error:
810 printf("Error parsing: %s\n", buffer);
811 exit(1);
812}