- Factor out .inf handling code from usetup
[reactos.git] / reactos / lib / inflib / infcore.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: .inf file parser
4 * FILE: lib/inflib/infcore.c
5 * PURPOSE: INF file parser that caches contents of INF file in memory
6 * PROGRAMMER: Royce Mitchell III
7 * Eric Kohl
8 * Ge van Geldorp
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include "inflib.h"
14
15 #define NDEBUG
16 #include <debug.h>
17
18 #define CONTROL_Z '\x1a'
19 #define MAX_SECTION_NAME_LEN 255
20 #define MAX_FIELD_LEN 511 /* larger fields get silently truncated */
21 /* actual string limit is MAX_INF_STRING_LENGTH+1 (plus terminating null) under Windows */
22 #define MAX_STRING_LEN (MAX_INF_STRING_LENGTH+1)
23
24
25 /* parser definitions */
26
27 enum parser_state
28 {
29 LINE_START, /* at beginning of a line */
30 SECTION_NAME, /* parsing a section name */
31 KEY_NAME, /* parsing a key name */
32 VALUE_NAME, /* parsing a value name */
33 EOL_BACKSLASH, /* backslash at end of line */
34 QUOTES, /* inside quotes */
35 LEADING_SPACES, /* leading spaces */
36 TRAILING_SPACES, /* trailing spaces */
37 COMMENT, /* inside a comment */
38 NB_PARSER_STATES
39 };
40
41 struct parser
42 {
43 const CHAR *start; /* start position of item being parsed */
44 const CHAR *end; /* end of buffer */
45 PINFCACHE file; /* file being built */
46 enum parser_state state; /* current parser state */
47 enum parser_state stack[4]; /* state stack */
48 int stack_pos; /* current pos in stack */
49
50 PINFCACHESECTION cur_section; /* pointer to the section being parsed*/
51 PINFCACHELINE line; /* current line */
52 unsigned int line_pos; /* current line position in file */
53 unsigned int error; /* error code */
54 unsigned int token_len; /* current token len */
55 TCHAR token[MAX_FIELD_LEN+1]; /* current token */
56 };
57
58 typedef const CHAR * (*parser_state_func)( struct parser *parser, const CHAR *pos );
59
60 /* parser state machine functions */
61 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos );
62 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos );
63 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos );
64 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos );
65 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos );
66 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos );
67 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos );
68 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos );
69 static const CHAR *comment_state( struct parser *parser, const CHAR *pos );
70
71 static const parser_state_func parser_funcs[NB_PARSER_STATES] =
72 {
73 line_start_state, /* LINE_START */
74 section_name_state, /* SECTION_NAME */
75 key_name_state, /* KEY_NAME */
76 value_name_state, /* VALUE_NAME */
77 eol_backslash_state, /* EOL_BACKSLASH */
78 quotes_state, /* QUOTES */
79 leading_spaces_state, /* LEADING_SPACES */
80 trailing_spaces_state, /* TRAILING_SPACES */
81 comment_state /* COMMENT */
82 };
83
84
85 /* PRIVATE FUNCTIONS ********************************************************/
86
87 static PINFCACHELINE
88 InfpCacheFreeLine (PINFCACHELINE Line)
89 {
90 PINFCACHELINE Next;
91 PINFCACHEFIELD Field;
92
93 if (Line == NULL)
94 {
95 return NULL;
96 }
97
98 Next = Line->Next;
99 if (Line->Key != NULL)
100 {
101 FREE (Line->Key);
102 Line->Key = NULL;
103 }
104
105 /* Remove data fields */
106 while (Line->FirstField != NULL)
107 {
108 Field = Line->FirstField->Next;
109 FREE (Line->FirstField);
110 Line->FirstField = Field;
111 }
112 Line->LastField = NULL;
113
114 FREE (Line);
115
116 return Next;
117 }
118
119
120 PINFCACHESECTION
121 InfpCacheFreeSection (PINFCACHESECTION Section)
122 {
123 PINFCACHESECTION Next;
124
125 if (Section == NULL)
126 {
127 return NULL;
128 }
129
130 /* Release all keys */
131 Next = Section->Next;
132 while (Section->FirstLine != NULL)
133 {
134 Section->FirstLine = InfpCacheFreeLine (Section->FirstLine);
135 }
136 Section->LastLine = NULL;
137
138 FREE (Section);
139
140 return Next;
141 }
142
143
144 static PINFCACHESECTION
145 InfpCacheFindSection (PINFCACHE Cache,
146 PCTSTR Name)
147 {
148 PINFCACHESECTION Section = NULL;
149
150 if (Cache == NULL || Name == NULL)
151 {
152 return NULL;
153 }
154
155 /* iterate through list of sections */
156 Section = Cache->FirstSection;
157 while (Section != NULL)
158 {
159 if (_tcsicmp (Section->Name, Name) == 0)
160 {
161 return Section;
162 }
163
164 /* get the next section*/
165 Section = Section->Next;
166 }
167
168 return NULL;
169 }
170
171
172 static PINFCACHESECTION
173 InfpCacheAddSection (PINFCACHE Cache,
174 PTCHAR Name)
175 {
176 PINFCACHESECTION Section = NULL;
177 ULONG Size;
178
179 if (Cache == NULL || Name == NULL)
180 {
181 DPRINT("Invalid parameter\n");
182 return NULL;
183 }
184
185 /* Allocate and initialize the new section */
186 Size = sizeof(INFCACHESECTION) + (_tcslen (Name) * sizeof(TCHAR));
187 Section = (PINFCACHESECTION)MALLOC (Size);
188 if (Section == NULL)
189 {
190 DPRINT("MALLOC() failed\n");
191 return NULL;
192 }
193 ZEROMEMORY (Section,
194 Size);
195
196 /* Copy section name */
197 _tcscpy (Section->Name, Name);
198
199 /* Append section */
200 if (Cache->FirstSection == NULL)
201 {
202 Cache->FirstSection = Section;
203 Cache->LastSection = Section;
204 }
205 else
206 {
207 Cache->LastSection->Next = Section;
208 Section->Prev = Cache->LastSection;
209 Cache->LastSection = Section;
210 }
211
212 return Section;
213 }
214
215
216 static PINFCACHELINE
217 InfpCacheAddLine (PINFCACHESECTION Section)
218 {
219 PINFCACHELINE Line;
220
221 if (Section == NULL)
222 {
223 DPRINT("Invalid parameter\n");
224 return NULL;
225 }
226
227 Line = (PINFCACHELINE)MALLOC (sizeof(INFCACHELINE));
228 if (Line == NULL)
229 {
230 DPRINT("MALLOC() failed\n");
231 return NULL;
232 }
233 ZEROMEMORY(Line,
234 sizeof(INFCACHELINE));
235
236 /* Append line */
237 if (Section->FirstLine == NULL)
238 {
239 Section->FirstLine = Line;
240 Section->LastLine = Line;
241 }
242 else
243 {
244 Section->LastLine->Next = Line;
245 Line->Prev = Section->LastLine;
246 Section->LastLine = Line;
247 }
248 Section->LineCount++;
249
250 return Line;
251 }
252
253
254 static PVOID
255 InfpAddKeyToLine (PINFCACHELINE Line,
256 PTCHAR Key)
257 {
258 if (Line == NULL)
259 return NULL;
260
261 if (Line->Key != NULL)
262 return NULL;
263
264 Line->Key = (PTCHAR)MALLOC ((_tcslen (Key) + 1) * sizeof(TCHAR));
265 if (Line->Key == NULL)
266 return NULL;
267
268 _tcscpy (Line->Key, Key);
269
270 return (PVOID)Line->Key;
271 }
272
273
274 static PVOID
275 InfpAddFieldToLine (PINFCACHELINE Line,
276 PTCHAR Data)
277 {
278 PINFCACHEFIELD Field;
279 ULONG Size;
280
281 Size = sizeof(INFCACHEFIELD) + (_tcslen(Data) * sizeof(TCHAR));
282 Field = (PINFCACHEFIELD)MALLOC (Size);
283 if (Field == NULL)
284 {
285 return NULL;
286 }
287 ZEROMEMORY (Field,
288 Size);
289 _tcscpy (Field->Data, Data);
290
291 /* Append key */
292 if (Line->FirstField == NULL)
293 {
294 Line->FirstField = Field;
295 Line->LastField = Field;
296 }
297 else
298 {
299 Line->LastField->Next = Field;
300 Field->Prev = Line->LastField;
301 Line->LastField = Field;
302 }
303 Line->FieldCount++;
304
305 return (PVOID)Field;
306 }
307
308
309 PINFCACHELINE
310 InfpCacheFindKeyLine (PINFCACHESECTION Section,
311 PTCHAR Key)
312 {
313 PINFCACHELINE Line;
314
315 Line = Section->FirstLine;
316 while (Line != NULL)
317 {
318 if (Line->Key != NULL && _tcsicmp (Line->Key, Key) == 0)
319 {
320 return Line;
321 }
322
323 Line = Line->Next;
324 }
325
326 return NULL;
327 }
328
329
330 /* push the current state on the parser stack */
331 inline static void push_state( struct parser *parser, enum parser_state state )
332 {
333 // assert( parser->stack_pos < sizeof(parser->stack)/sizeof(parser->stack[0]) );
334 parser->stack[parser->stack_pos++] = state;
335 }
336
337
338 /* pop the current state */
339 inline static void pop_state( struct parser *parser )
340 {
341 // assert( parser->stack_pos );
342 parser->state = parser->stack[--parser->stack_pos];
343 }
344
345
346 /* set the parser state and return the previous one */
347 inline static enum parser_state set_state( struct parser *parser, enum parser_state state )
348 {
349 enum parser_state ret = parser->state;
350 parser->state = state;
351 return ret;
352 }
353
354
355 /* check if the pointer points to an end of file */
356 inline static int is_eof( struct parser *parser, const CHAR *ptr )
357 {
358 return (ptr >= parser->end || *ptr == CONTROL_Z);
359 }
360
361
362 /* check if the pointer points to an end of line */
363 inline static int is_eol( struct parser *parser, const CHAR *ptr )
364 {
365 return (ptr >= parser->end ||
366 *ptr == CONTROL_Z ||
367 *ptr == '\n' ||
368 (*ptr == '\r' && *(ptr + 1) == '\n'));
369 }
370
371
372 /* push data from current token start up to pos into the current token */
373 static int push_token( struct parser *parser, const CHAR *pos )
374 {
375 unsigned int len = pos - parser->start;
376 const CHAR *src = parser->start;
377 TCHAR *dst = parser->token + parser->token_len;
378
379 if (len > MAX_FIELD_LEN - parser->token_len)
380 len = MAX_FIELD_LEN - parser->token_len;
381
382 parser->token_len += len;
383 for ( ; len > 0; len--, dst++, src++)
384 *dst = *src ? (TCHAR)*src : L' ';
385 *dst = 0;
386 parser->start = pos;
387
388 return 0;
389 }
390
391
392
393 /* add a section with the current token as name */
394 static PVOID add_section_from_token( struct parser *parser )
395 {
396 PINFCACHESECTION Section;
397
398 if (parser->token_len > MAX_SECTION_NAME_LEN)
399 {
400 parser->error = INF_STATUS_SECTION_NAME_TOO_LONG;
401 return NULL;
402 }
403
404 Section = InfpCacheFindSection (parser->file,
405 parser->token);
406 if (Section == NULL)
407 {
408 /* need to create a new one */
409 Section= InfpCacheAddSection (parser->file,
410 parser->token);
411 if (Section == NULL)
412 {
413 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
414 return NULL;
415 }
416 }
417
418 parser->token_len = 0;
419 parser->cur_section = Section;
420
421 return (PVOID)Section;
422 }
423
424
425 /* add a field containing the current token to the current line */
426 static struct field *add_field_from_token( struct parser *parser, int is_key )
427 {
428 PVOID field;
429
430 if (!parser->line) /* need to start a new line */
431 {
432 if (parser->cur_section == NULL) /* got a line before the first section */
433 {
434 parser->error = INF_STATUS_WRONG_INF_STYLE;
435 return NULL;
436 }
437
438 parser->line = InfpCacheAddLine (parser->cur_section);
439 if (parser->line == NULL)
440 goto error;
441 }
442 else
443 {
444 // assert(!is_key);
445 }
446
447 if (is_key)
448 {
449 field = InfpAddKeyToLine(parser->line, parser->token);
450 }
451 else
452 {
453 field = InfpAddFieldToLine(parser->line, parser->token);
454 }
455
456 if (field != NULL)
457 {
458 parser->token_len = 0;
459 return field;
460 }
461
462 error:
463 parser->error = INF_STATUS_NOT_ENOUGH_MEMORY;
464 return NULL;
465 }
466
467
468 /* close the current line and prepare for parsing a new one */
469 static void close_current_line( struct parser *parser )
470 {
471 parser->line = NULL;
472 }
473
474
475
476 /* handler for parser LINE_START state */
477 static const CHAR *line_start_state( struct parser *parser, const CHAR *pos )
478 {
479 const CHAR *p;
480
481 for (p = pos; !is_eof( parser, p ); p++)
482 {
483 switch(*p)
484 {
485 case '\r':
486 continue;
487
488 case '\n':
489 parser->line_pos++;
490 close_current_line( parser );
491 break;
492
493 case ';':
494 push_state( parser, LINE_START );
495 set_state( parser, COMMENT );
496 return p + 1;
497
498 case '[':
499 parser->start = p + 1;
500 set_state( parser, SECTION_NAME );
501 return p + 1;
502
503 default:
504 if (!isspace(*p))
505 {
506 parser->start = p;
507 set_state( parser, KEY_NAME );
508 return p;
509 }
510 break;
511 }
512 }
513 close_current_line( parser );
514 return NULL;
515 }
516
517
518 /* handler for parser SECTION_NAME state */
519 static const CHAR *section_name_state( struct parser *parser, const CHAR *pos )
520 {
521 const CHAR *p;
522
523 for (p = pos; !is_eol( parser, p ); p++)
524 {
525 if (*p == ']')
526 {
527 push_token( parser, p );
528 if (add_section_from_token( parser ) == NULL)
529 return NULL;
530 push_state( parser, LINE_START );
531 set_state( parser, COMMENT ); /* ignore everything else on the line */
532 return p + 1;
533 }
534 }
535 parser->error = INF_STATUS_BAD_SECTION_NAME_LINE; /* unfinished section name */
536 return NULL;
537 }
538
539
540 /* handler for parser KEY_NAME state */
541 static const CHAR *key_name_state( struct parser *parser, const CHAR *pos )
542 {
543 const CHAR *p, *token_end = parser->start;
544
545 for (p = pos; !is_eol( parser, p ); p++)
546 {
547 if (*p == ',') break;
548 switch(*p)
549 {
550
551 case '=':
552 push_token( parser, token_end );
553 if (!add_field_from_token( parser, 1 )) return NULL;
554 parser->start = p + 1;
555 push_state( parser, VALUE_NAME );
556 set_state( parser, LEADING_SPACES );
557 return p + 1;
558 case ';':
559 push_token( parser, token_end );
560 if (!add_field_from_token( parser, 0 )) return NULL;
561 push_state( parser, LINE_START );
562 set_state( parser, COMMENT );
563 return p + 1;
564 case '"':
565 push_token( parser, token_end );
566 parser->start = p + 1;
567 push_state( parser, KEY_NAME );
568 set_state( parser, QUOTES );
569 return p + 1;
570 case '\\':
571 push_token( parser, token_end );
572 parser->start = p;
573 push_state( parser, KEY_NAME );
574 set_state( parser, EOL_BACKSLASH );
575 return p;
576 default:
577 if (!isspace(*p)) token_end = p + 1;
578 else
579 {
580 push_token( parser, p );
581 push_state( parser, KEY_NAME );
582 set_state( parser, TRAILING_SPACES );
583 return p;
584 }
585 break;
586 }
587 }
588 push_token( parser, token_end );
589 set_state( parser, VALUE_NAME );
590 return p;
591 }
592
593
594 /* handler for parser VALUE_NAME state */
595 static const CHAR *value_name_state( struct parser *parser, const CHAR *pos )
596 {
597 const CHAR *p, *token_end = parser->start;
598
599 for (p = pos; !is_eol( parser, p ); p++)
600 {
601 switch(*p)
602 {
603 case ';':
604 push_token( parser, token_end );
605 if (!add_field_from_token( parser, 0 )) return NULL;
606 push_state( parser, LINE_START );
607 set_state( parser, COMMENT );
608 return p + 1;
609 case ',':
610 push_token( parser, token_end );
611 if (!add_field_from_token( parser, 0 )) return NULL;
612 parser->start = p + 1;
613 push_state( parser, VALUE_NAME );
614 set_state( parser, LEADING_SPACES );
615 return p + 1;
616 case '"':
617 push_token( parser, token_end );
618 parser->start = p + 1;
619 push_state( parser, VALUE_NAME );
620 set_state( parser, QUOTES );
621 return p + 1;
622 case '\\':
623 push_token( parser, token_end );
624 parser->start = p;
625 push_state( parser, VALUE_NAME );
626 set_state( parser, EOL_BACKSLASH );
627 return p;
628 default:
629 if (!isspace(*p)) token_end = p + 1;
630 else
631 {
632 push_token( parser, p );
633 push_state( parser, VALUE_NAME );
634 set_state( parser, TRAILING_SPACES );
635 return p;
636 }
637 break;
638 }
639 }
640 push_token( parser, token_end );
641 if (!add_field_from_token( parser, 0 )) return NULL;
642 set_state( parser, LINE_START );
643 return p;
644 }
645
646
647 /* handler for parser EOL_BACKSLASH state */
648 static const CHAR *eol_backslash_state( struct parser *parser, const CHAR *pos )
649 {
650 const CHAR *p;
651
652 for (p = pos; !is_eof( parser, p ); p++)
653 {
654 switch(*p)
655 {
656 case '\r':
657 continue;
658
659 case '\n':
660 parser->line_pos++;
661 parser->start = p + 1;
662 set_state( parser, LEADING_SPACES );
663 return p + 1;
664
665 case '\\':
666 continue;
667
668 case ';':
669 push_state( parser, EOL_BACKSLASH );
670 set_state( parser, COMMENT );
671 return p + 1;
672
673 default:
674 if (isspace(*p))
675 continue;
676 push_token( parser, p );
677 pop_state( parser );
678 return p;
679 }
680 }
681 parser->start = p;
682 pop_state( parser );
683
684 return p;
685 }
686
687
688 /* handler for parser QUOTES state */
689 static const CHAR *quotes_state( struct parser *parser, const CHAR *pos )
690 {
691 const CHAR *p, *token_end = parser->start;
692
693 for (p = pos; !is_eol( parser, p ); p++)
694 {
695 if (*p == '"')
696 {
697 if (p+1 < parser->end && p[1] == '"') /* double quotes */
698 {
699 push_token( parser, p + 1 );
700 parser->start = token_end = p + 2;
701 p++;
702 }
703 else /* end of quotes */
704 {
705 push_token( parser, p );
706 parser->start = p + 1;
707 pop_state( parser );
708 return p + 1;
709 }
710 }
711 }
712 push_token( parser, p );
713 pop_state( parser );
714 return p;
715 }
716
717
718 /* handler for parser LEADING_SPACES state */
719 static const CHAR *leading_spaces_state( struct parser *parser, const CHAR *pos )
720 {
721 const CHAR *p;
722
723 for (p = pos; !is_eol( parser, p ); p++)
724 {
725 if (*p == '\\')
726 {
727 parser->start = p;
728 set_state( parser, EOL_BACKSLASH );
729 return p;
730 }
731 if (!isspace(*p))
732 break;
733 }
734 parser->start = p;
735 pop_state( parser );
736 return p;
737 }
738
739
740 /* handler for parser TRAILING_SPACES state */
741 static const CHAR *trailing_spaces_state( struct parser *parser, const CHAR *pos )
742 {
743 const CHAR *p;
744
745 for (p = pos; !is_eol( parser, p ); p++)
746 {
747 if (*p == '\\')
748 {
749 set_state( parser, EOL_BACKSLASH );
750 return p;
751 }
752 if (!isspace(*p))
753 break;
754 }
755 pop_state( parser );
756 return p;
757 }
758
759
760 /* handler for parser COMMENT state */
761 static const CHAR *comment_state( struct parser *parser, const CHAR *pos )
762 {
763 const CHAR *p = pos;
764
765 while (!is_eol( parser, p ))
766 p++;
767 pop_state( parser );
768 return p;
769 }
770
771
772 /* parse a complete buffer */
773 INFSTATUS
774 InfpParseBuffer (PINFCACHE file,
775 const CHAR *buffer,
776 const CHAR *end,
777 PULONG error_line)
778 {
779 struct parser parser;
780 const CHAR *pos = buffer;
781
782 parser.start = buffer;
783 parser.end = end;
784 parser.file = file;
785 parser.line = NULL;
786 parser.state = LINE_START;
787 parser.stack_pos = 0;
788 parser.cur_section = NULL;
789 parser.line_pos = 1;
790 parser.error = 0;
791 parser.token_len = 0;
792
793 /* parser main loop */
794 while (pos)
795 pos = (parser_funcs[parser.state])(&parser, pos);
796
797 if (parser.error)
798 {
799 if (error_line)
800 *error_line = parser.line_pos;
801 return parser.error;
802 }
803
804 /* find the [strings] section */
805 file->StringsSection = InfpCacheFindSection (file,
806 _T("Strings"));
807
808 return INF_STATUS_SUCCESS;
809 }
810
811 /* EOF */