=== added file 'ChangeLog.scripting' --- old/ChangeLog.scripting 1970-01-01 00:00:00 +0000 +++ new/ChangeLog.scripting 2009-12-20 08:28:15 +0000 @@ -0,0 +1,47 @@ +2009-12-20 BVK Chaitanya + + * conf/any-emu.rmk: Makerules for flex based lexer. + * conf/common.rmk: Likewise. + * conf/i386-coreboot.rmk: Likewise. + * conf/i386-efi.rmk: Likewise. + * conf/i386-ieee1275.rmk: Likewise. + * conf/i386-pc.rmk: Likewise. + * conf/mips.rmk: Likewise. + * conf/powerpc-ieee1275.rmk: Likewise. + * conf/x86_64-efi.rmk: Likewise. + * configure.ac: Configure rule for flex. + * include/grub/script_sh.h (grub_lexer_param): Simplified. + (grub_script_lexer_fini): New function to deinit grub_lexer_param. + (grub_script_lexer_init): Takes grub_parser_param parameter. + (grub_script_lexer_record_start): Likewise. + (grub_script_lexer_record_stop): Likewise. + (grub_script_lexer_yywrap): New function to feed more data to + lexer. + (grub_script_lexer_record): New function to save source code + tokens. + (grub_script_yylex): Rewrote to use yylex for token parsing. + * script/lexer.c: New defintions for above functions. + * script/parser.y: Better and more complete grammar. + * script/script.c: Cleanup. + (grub_script_parse): grub_lexer_param deinitialized using new + grub_script_lexer_fini function. + * script/yylex.l: Newfile. Flex script for GRUB Script. + +2009-12-20 BVK Chaitanya + + Added --verbose (-v) option to grub-script-check. + + * util/grub-script-check.c (main): Added --verbose option. + +2009-12-19 BVK Chaitanya + + Added grub-script-check utility. + + * util/grub-script-check.c: Newfile. + * conf/common.rmk: Makerules for building grub-script-check. + +2009-12-17 BVK Chaitanya + + Bug fixes in GRUB script parsing. + + * script/parser.y: Better grammar. === added file 'ChangeLog.unit-testing' --- old/ChangeLog.unit-testing 1970-01-01 00:00:00 +0000 +++ new/ChangeLog.unit-testing 2009-12-20 12:14:10 +0000 @@ -0,0 +1,35 @@ +2009-12-20 BVK Chaitanya + + * Makefile.in: Unit tests are skipped for platform=emu. + +2009-12-19 BVK Chaitanya + + New unit tests for GRUB script parser. + + * conf/tests.rmk: Make rules for new unit tests. + * tests/grub_script_echo1.in: New unit test. + * tests/grub_script_functions1.in: New unit test. + + +2009-12-17 BVK Chaitanya + + Unit testing framework for GRUB + + * Makefile.in: Added make check rules. + * genmk.rb: Added TESTS type target for unit tests. + * ChangeLog.unit-testing: Newfile. ChangeLog for unit tests + branch. + * conf/tests.rmk: Newfile. Makefile for tests and test framework. + * conf/util/grub-shell-tester.in: Newfile. Tool for runing grub + script tests. + * include/grub/test.h: Newfile. Testing framework header file. + * tests/test.c: Newfile. Common definitions for testing framework. + * test/unit_test.c: Newfile. Unit tests framework. + * tests/functional_test.c: Newfile. Functional tests framework. + * tests/example_functional_test.c: Newfile. Functional test + example. + * tests/example_scripted_test.in: Newfile. Scripted test example. + * tests/example_unit_test.c: Newfile. Unit test example. + * tests/grub_sprintf.c: Newfile. Unittest for grub_printf logic. + * tests/gettext_1.in: Newfile. Scripted test for gettext. + * tests/echo_keywords.in: Newfile. Scripted test for GRUB parser. === modified file 'Makefile.in' --- old/Makefile.in 2009-12-13 20:25:49 +0000 +++ new/Makefile.in 2009-12-20 12:14:10 +0000 @@ -138,6 +138,7 @@ SCRIPTS = $(bin_SCRIPTS) $(sbin_SCRIPTS) $(grub-mkconfig_SCRIPTS) \ $(lib_SCRIPTS) INFOS = $(info_INFOS) +TESTS = $(check_TESTS) $(check_SCRIPTS) $(check_MODULES) CLEANFILES = MOSTLYCLEANFILES = @@ -164,6 +165,8 @@ include $(srcdir)/conf/any-emu.mk else include $(srcdir)/conf/$(target_cpu)-$(platform).mk +# For tests. +include $(srcdir)/conf/tests.mk # For external modules. -include $(wildcard $(GRUB_CONTRIB)/*/conf/common.mk) endif @@ -452,7 +455,15 @@ @echo "$(distdir).tar.gz is ready for distribution" | \ sed 'h;s/./=/g;p;x;p;x' -check: +check: all $(TESTS) + @list="$(check_TESTS) $(check_SCRIPTS)"; \ + for file in $$list; do \ + if $(builddir)/$$file; then \ + echo "$$file: PASS"; \ + else \ + echo "$$file: FAIL"; \ + fi; \ + done .SUFFIX: .SUFFIX: .c .o .S .d === modified file 'conf/any-emu.rmk' --- old/conf/any-emu.rmk 2009-12-05 19:47:36 +0000 +++ new/conf/any-emu.rmk 2009-12-20 08:28:15 +0000 @@ -1,7 +1,7 @@ # -*- makefile -*- # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h sbin_UTILITIES += grub-emu util/grub-emu.c_DEPENDENCIES = grub_emu_init.h @@ -32,6 +32,7 @@ normal/menu_text.c \ script/main.c script/execute.c script/function.c \ script/lexer.c script/script.c grub_script.tab.c \ + grub_script.yy.c \ partmap/amiga.c partmap/apple.c partmap/msdos.c partmap/sun.c \ partmap/acorn.c partmap/gpt.c \ \ @@ -123,3 +124,9 @@ grub_script.tab.c grub_script.tab.h: script/parser.y $(YACC) -d -p grub_script_yy -b grub_script $(srcdir)/script/parser.y DISTCLEANFILES += grub_script.tab.c grub_script.tab.h + +grub_script.yy.c grub_script.yy.h: script/yylex.l + $(LEX) -o grub_script.yy.c --header-file=grub_script.yy.h $(srcdir)/script/yylex.l + sed -i 's/^#include.*\(\|\|\|\|\)//g' grub_script.yy.h + sed -i 's/^#include.*\(\|\|\|\|\)//g' grub_script.yy.c +DISTCLEANFILES += grub_script.yy.c grub_script.yy.h === modified file 'conf/common.rmk' --- old/conf/common.rmk 2009-12-09 21:03:26 +0000 +++ new/conf/common.rmk 2009-12-20 08:28:15 +0000 @@ -88,11 +88,40 @@ bin_UTILITIES += grub-mkrelpath grub_mkrelpath_SOURCES = gnulib/progname.c util/grub-mkrelpath.c util/misc.c +# For the lexer. +grub_script.yy.c grub_script.yy.h: script/yylex.l + $(LEX) -o grub_script.yy.c --header-file=grub_script.yy.h $(srcdir)/script/yylex.l + sed -i 's/^#include.*\(\|\|\|\|\)//g' grub_script.yy.h + sed -i 's/^#include.*\(\|\|\|\|\)//g' grub_script.yy.c +DISTCLEANFILES += grub_script.yy.c grub_script.yy.h + +# For grub-script-check. +bin_UTILITIES += grub-script-check +util/grub-script-check.c_DEPENDENCIES = grub_script_check_init.h +grub_script_check_SOURCES = gnulib/progname.c util/grub-script-check.c util/misc.c \ + script/main.c script/script.c script/function.c script/lexer.c \ + kern/handler.c kern/err.c kern/parser.c kern/list.c \ + kern/misc.c kern/env.c grub_script_check_init.c grub_script.tab.c \ + grub_script.yy.c + # For the parser. grub_script.tab.c grub_script.tab.h: script/parser.y $(YACC) -d -p grub_script_yy -b grub_script $(srcdir)/script/parser.y DISTCLEANFILES += grub_script.tab.c grub_script.tab.h +# For grub-check. +grub_script_check_init.lst: geninit.sh $(filter-out grub_script_check_init.c,$(grub_script_check_SOURCES)) + rm -f $@; grep GRUB_MOD_INIT $(filter %.c,$^) /dev/null > $@ +DISTCLEANFILES += grub_script_check_init.lst + +grub_script_check_init.h: grub_script_check_init.lst $(filter-out grub_script_check_init.c,$(grub_script_check_SOURCES)) geninitheader.sh + rm -f $@; sh $(srcdir)/geninitheader.sh $< > $@ +DISTCLEANFILES += grub_script_check_init.h + +grub_script_check_init.c: grub_script_check_init.lst $(filter-out grub_script_check_init.c,$(grub_script_check_SOURCES)) geninit.sh + rm -f $@; sh $(srcdir)/geninit.sh $< $(filter %.c,$^) > $@ +DISTCLEANFILES += grub_script_check_init.c + # For grub-probe. grub_probe_init.lst: geninit.sh $(filter-out grub_probe_init.c,$(grub_probe_SOURCES)) rm -f $@; grep GRUB_MOD_INIT $(filter %.c,$^) /dev/null > $@ @@ -592,7 +621,7 @@ # For sh.mod. sh_mod_SOURCES = script/main.c script/script.c script/execute.c \ - script/function.c script/lexer.c grub_script.tab.c + script/function.c script/lexer.c grub_script.tab.c grub_script.yy.c sh_mod_CFLAGS = $(COMMON_CFLAGS) sh_mod_LDFLAGS = $(COMMON_LDFLAGS) === modified file 'conf/i386-coreboot.rmk' --- old/conf/i386-coreboot.rmk 2009-12-12 13:39:59 +0000 +++ new/conf/i386-coreboot.rmk 2009-12-20 08:28:15 +0000 @@ -5,7 +5,7 @@ COMMON_LDFLAGS = -m32 -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Images. === modified file 'conf/i386-efi.rmk' --- old/conf/i386-efi.rmk 2009-12-13 20:25:49 +0000 +++ new/conf/i386-efi.rmk 2009-12-20 08:28:15 +0000 @@ -5,7 +5,7 @@ COMMON_LDFLAGS = -melf_i386 -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Utilities. bin_UTILITIES = grub-mkimage === modified file 'conf/i386-ieee1275.rmk' --- old/conf/i386-ieee1275.rmk 2009-12-12 13:39:59 +0000 +++ new/conf/i386-ieee1275.rmk 2009-12-20 08:28:15 +0000 @@ -5,7 +5,7 @@ COMMON_LDFLAGS = -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Images. pkglib_PROGRAMS = kernel.img === modified file 'conf/i386-pc.rmk' --- old/conf/i386-pc.rmk 2009-12-13 20:25:49 +0000 +++ new/conf/i386-pc.rmk 2009-12-20 08:28:15 +0000 @@ -7,7 +7,7 @@ COMMON_LDFLAGS = -m32 -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Images. pkglib_IMAGES = boot.img cdboot.img diskboot.img kernel.img lnxboot.img \ === modified file 'conf/mips.rmk' --- old/conf/mips.rmk 2009-12-05 17:46:28 +0000 +++ new/conf/mips.rmk 2009-12-20 08:28:15 +0000 @@ -6,7 +6,7 @@ COMMON_LDFLAGS += -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Images. === modified file 'conf/powerpc-ieee1275.rmk' --- old/conf/powerpc-ieee1275.rmk 2009-12-05 15:42:20 +0000 +++ new/conf/powerpc-ieee1275.rmk 2009-12-20 08:28:15 +0000 @@ -6,7 +6,7 @@ COMMON_LDFLAGS += -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Images. === added file 'conf/tests.rmk' --- old/conf/tests.rmk 1970-01-01 00:00:00 +0000 +++ new/conf/tests.rmk 2009-12-20 08:28:15 +0000 @@ -0,0 +1,47 @@ +# -*- makefile -*- + +# For grub-mkconfig +grub-shell-tester: tests/util/grub-shell-tester.in config.status + ./config.status --file=$@:$< + chmod +x $@ +bin_SCRIPTS += grub-shell-tester +CLEANFILES += grub-shell-tester + +check_MODULES += functional_test.mod +functional_test_mod_SOURCES = tests/functional_test.c tests/test.c +functional_test_mod_CFLAGS = $(COMMON_CFLAGS) +functional_test_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# Unit tests + +check_TESTS += example_unit_test +example_unit_test_SOURCES = tests/example_unit_test.c kern/list.c kern/misc.c tests/test.c tests/unit_test.c +example_unit_test_CFLAGS = -Wno-format + +check_TESTS += grub_sprintf +grub_sprintf_SOURCES = tests/grub_sprintf.c kern/misc.c kern/list.c tests/test.c tests/unit_test.c +grub_sprintf_CFLAGS = -Wno-error -Wno-format + +# Functional tests + +check_MODULES += example_functional_test.mod +example_functional_test_mod_SOURCES = tests/example_functional_test.c +example_functional_test_mod_CFLAGS = -Wno-format $(COMMON_CFLAGS) +example_functional_test_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# Scripted tests + +check_SCRIPTS += example_scripted_test +example_scripted_test_SOURCES = tests/example_scripted_test.in + +check_SCRIPTS += echo_keywords +echo_keywords_SOURCES = tests/echo_keywords.in + +check_SCRIPTS += gettext_1 +gettext_1_SOURCES = tests/gettext_1.in + +check_SCRIPTS += grub_script_echo1 +grub_script_echo1_SOURCES = tests/grub_script_echo1.in + +check_SCRIPTS += grub_script_functions1 +grub_script_functions1_SOURCES = tests/grub_script_functions1.in === modified file 'conf/x86_64-efi.rmk' --- old/conf/x86_64-efi.rmk 2009-12-13 20:25:49 +0000 +++ new/conf/x86_64-efi.rmk 2009-12-20 08:28:15 +0000 @@ -5,7 +5,7 @@ COMMON_LDFLAGS = -melf_x86_64 -nostdlib # Used by various components. These rules need to precede them. -script/lexer.c_DEPENDENCIES = grub_script.tab.h +script/lexer.c_DEPENDENCIES = grub_script.tab.h grub_script.yy.h # Utilities. bin_UTILITIES = grub-mkimage === modified file 'configure.ac' --- old/configure.ac 2009-12-12 13:39:59 +0000 +++ new/configure.ac 2009-12-20 08:28:15 +0000 @@ -158,6 +158,11 @@ AC_MSG_ERROR([cmp is not found]) fi +AC_CHECK_PROGS([LEX], [flex]) +if test "x$LEX" = x; then + AC_MSG_ERROR([flex is not found]) +fi + AC_CHECK_PROGS([YACC], [bison]) if test "x$YACC" = x; then AC_MSG_ERROR([bison is not found]) === modified file 'genmk.rb' --- old/genmk.rb 2009-12-13 20:25:49 +0000 +++ new/genmk.rb 2009-12-20 08:28:15 +0000 @@ -413,7 +413,7 @@ PModule.new(prefix, pmod) end - when 'UTILITIES' + when 'UTILITIES', 'TESTS' utils += args.split(/\s+/).collect do |util| Utility.new(prefix, util) end === modified file 'include/grub/script_sh.h' --- old/include/grub/script_sh.h 2009-06-17 13:47:37 +0000 +++ new/include/grub/script_sh.h 2009-12-20 08:28:15 +0000 @@ -112,7 +112,7 @@ struct grub_script_arglist *arglist; /* The sourcecode the entry will be generated from. */ - const char *sourcecode; + char *sourcecode; /* Options. XXX: Not used yet. */ int options; @@ -121,12 +121,6 @@ /* State of the lexer as passed to the lexer. */ struct grub_lexer_param { - /* Set to 0 when the lexer is done. */ - int done; - - /* State of the state machine. */ - grub_parser_state_t state; - /* Function used by the lexer to get a new line when more input is expected, but not available. */ grub_reader_getline_t getline; @@ -137,10 +131,6 @@ depleted. */ int refs; - /* The character stream that has to be parsed. */ - char *script; - char *newscript; /* XXX */ - /* While walking through the databuffer, `record' the characters to this other buffer. It can be used to edit the menu entry at a later moment. */ @@ -157,13 +147,29 @@ /* Size of RECORDING. */ int recordlen; - /* The token that is already parsed but not yet returned. */ - int tokenonhold; - - /* Was the last token a newline? */ - int was_newline; + /* End of file reached. */ + int eof; + + /* Merge multiple word tokens. */ + int merge_start; + int merge_end; + + /* Text of current token. */ + char *text; + + /* Flex scanner. */ + void *yyscanner; + + /* Flex scanner buffer. */ + void *buffer; + + /* Length of current token text. */ + unsigned size; }; +#define GRUB_LEXER_TOKEN_MAX 256 +#define GRUB_LEXER_RECORD_INCREMENT 256 + /* State of the parser as passes to the parser. */ struct grub_parser_param { @@ -223,12 +229,16 @@ struct grub_script *grub_script_create (struct grub_script_cmd *cmd, struct grub_script_mem *mem); -struct grub_lexer_param *grub_script_lexer_init (char *s, +struct grub_lexer_param *grub_script_lexer_init (struct grub_parser_param *parser, + char *script, grub_reader_getline_t getline); +void grub_script_lexer_fini (struct grub_lexer_param *); void grub_script_lexer_ref (struct grub_lexer_param *); void grub_script_lexer_deref (struct grub_lexer_param *); -void grub_script_lexer_record_start (struct grub_lexer_param *); -char *grub_script_lexer_record_stop (struct grub_lexer_param *); +void grub_script_lexer_record_start (struct grub_parser_param *); +char *grub_script_lexer_record_stop (struct grub_parser_param *); +int grub_script_lexer_yywrap (struct grub_parser_param *); +void grub_script_lexer_record (struct grub_parser_param *, char *); /* Functions to track allocated memory. */ struct grub_script_mem *grub_script_mem_record (struct grub_parser_param *state); === added file 'include/grub/test.h' --- old/include/grub/test.h 1970-01-01 00:00:00 +0000 +++ new/include/grub/test.h 2009-12-20 08:28:15 +0000 @@ -0,0 +1,84 @@ +#ifndef GRUB_TEST_HEADER +#define GRUB_TEST_HEADER + +#include +#include +#include +#include +#include + +struct grub_test +{ + /* The next test. */ + struct grub_test *next; + + /* The test name. */ + char *name; + + /* The test main function. */ + void (*main) (void); +}; +typedef struct grub_test *grub_test_t; + +extern grub_test_t EXPORT_VAR (grub_test_list); + +void EXPORT_FUNC (grub_test_register) (const char *name, void (*test) (void)); + +void EXPORT_FUNC (grub_test_unregister) (const char *name); + +/* Execute a test and print results. */ +int grub_test_run (const char *name); + +/* Test `cond' for nonzero; log failure otherwise. */ +void grub_test_nonzero (int cond, const char *file, + const char *func, grub_uint32_t line, + const char *fmt, ...) + __attribute__ ((format (printf, 5, 6))); + +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif + +/* Macro to fill in location details and an optional error message. */ +#define grub_test_assert(cond, ...) \ + grub_test_nonzero(cond, __FILE__, __func__, __LINE__, \ + ## __VA_ARGS__, \ + "assert failed: %s", #cond) + +/* Macro to define a unit test. */ +#define GRUB_UNIT_TEST(name, funp) \ + void grub_unit_test_init (void) \ + { \ + grub_test_register (name, funp); \ + } \ + \ + void grub_unit_test_fini (void) \ + { \ + grub_test_unregister (name); \ + } + +/* Macro to define a functional test. */ +#define GRUB_FUNCTIONAL_TEST(name, funp) \ + GRUB_MOD_INIT(functional_test_##funp) \ + { \ + grub_test_register (name, funp); \ + } \ + \ + GRUB_MOD_FINI(functional_test_##funp) \ + { \ + grub_test_unregister (name); \ + } + +/* Functions that are defined differently for unit and functional tests. */ +void *grub_test_malloc (grub_size_t size); +void grub_test_free (void *ptr); +char *grub_test_strdup (const char *str); +int grub_test_vsprintf (char *str, const char *fmt, va_list args); +int grub_test_printf (const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +#endif /* ! GRUB_TEST_HEADER */ === modified file 'script/lexer.c' --- old/script/lexer.c 2009-11-23 15:37:33 +0000 +++ new/script/lexer.c 2009-12-20 08:28:15 +0000 @@ -23,42 +23,7 @@ #include #include "grub_script.tab.h" - -static int -check_varstate (grub_parser_state_t state) -{ - return (state == GRUB_PARSER_STATE_VARNAME - || state == GRUB_PARSER_STATE_VAR - || state == GRUB_PARSER_STATE_QVAR - || state == GRUB_PARSER_STATE_VARNAME2 - || state == GRUB_PARSER_STATE_QVARNAME - || state == GRUB_PARSER_STATE_QVARNAME2); -} - -static int -check_textstate (grub_parser_state_t state) -{ - return (state == GRUB_PARSER_STATE_TEXT - || state == GRUB_PARSER_STATE_ESC - || state == GRUB_PARSER_STATE_QUOTE - || state == GRUB_PARSER_STATE_DQUOTE); -} - -struct grub_lexer_param * -grub_script_lexer_init (char *script, grub_reader_getline_t getline) -{ - struct grub_lexer_param *param; - - param = grub_zalloc (sizeof (*param)); - if (! param) - return 0; - - param->state = GRUB_PARSER_STATE_TEXT; - param->getline = getline; - param->script = script; - - return param; -} +#include "grub_script.yy.h" void grub_script_lexer_ref (struct grub_lexer_param *state) @@ -74,360 +39,292 @@ /* Start recording all characters passing through the lexer. */ void -grub_script_lexer_record_start (struct grub_lexer_param *state) +grub_script_lexer_record_start (struct grub_parser_param *parser) { - state->record = 1; - state->recordlen = 100; - state->recording = grub_malloc (state->recordlen); - state->recordpos = 0; + struct grub_lexer_param *lexer = parser->lexerstate; + + lexer->record = 1; + lexer->recordpos = 0; + if (lexer->recording) /* reuse last record */ + return; + + lexer->recordlen = GRUB_LEXER_RECORD_INCREMENT; + lexer->recording = grub_malloc (lexer->recordlen); + + if (!lexer->recording) + { + grub_script_yyerror (parser, "out of memory"); + lexer->record = 0; + lexer->recordlen = 0; + } } char * -grub_script_lexer_record_stop (struct grub_lexer_param *state) +grub_script_lexer_record_stop (struct grub_parser_param *parser) { - state->record = 0; - - /* Delete the last character, it is a `}'. */ - if (state->recordpos > 0) - { - if (state->recording[--state->recordpos] != '}') - { - grub_printf ("Internal error while parsing menu entry"); - for (;;); /* XXX */ - } - state->recording[state->recordpos] = '\0'; - } - - return state->recording; + char *ptr; + struct grub_lexer_param *lexer = parser->lexerstate; + + if (!lexer->record || !lexer->recording) + return 0; + + /* Delete '{' and '}' characters. */ + /* XXX This is not necessary in BASH. */ + + ptr = lexer->recording + lexer->recordpos - 1; + while (*ptr && grub_isspace (*ptr)) + ptr--; + if (*ptr == '}') + *ptr = '\0'; + + ptr = lexer->recording; + while (*ptr && grub_isspace (*ptr)) + ptr++; + if (*ptr == '{') + ptr++; + + lexer->record = 0; + lexer->recordpos = 0; + return (*ptr ? grub_strdup (ptr) : 0); } -/* When recording is enabled, record the character C as the next item - in the character stream. */ -static void -recordchar (struct grub_lexer_param *state, char c) + +/* Record STR if input recording is enabled. */ +void +grub_script_lexer_record (struct grub_parser_param *parser, char *str) { - if (state->recordpos == state->recordlen) + int len; + struct grub_lexer_param *lexer = parser->lexerstate; + + if (!lexer->record) + return; + + len = grub_strlen (str); + if (lexer->recordpos + len >= lexer->recordlen - 1) { - char *old = state->recording; - state->recordlen += 100; - state->recording = grub_realloc (state->recording, state->recordlen); - if (! state->recording) + char *old = lexer->recording; + lexer->recordlen += GRUB_LEXER_RECORD_INCREMENT; + lexer->recording = grub_realloc (lexer->recording, lexer->recordlen); + if (!lexer->recording) { grub_free (old); - state->record = 0; - } - } - state->recording[state->recordpos++] = c; -} - -/* Fetch the next character for the lexer. */ -static void -nextchar (struct grub_lexer_param *state) -{ - if (state->record) - recordchar (state, *state->script); - state->script++; -} - -int -grub_script_yylex (union YYSTYPE *yylval, struct grub_parser_param *parsestate) -{ - grub_parser_state_t newstate; - char use; - struct grub_lexer_param *state = parsestate->lexerstate; - int firstrun = 1; - - yylval->arg = 0; - - if (state->tokenonhold) - { - int token = state->tokenonhold; - state->tokenonhold = 0; - return token; - } - - for (;! state->done; firstrun = 0) - { - if (! state->script || ! *state->script) - { - /* Check if more tokens are requested by the parser. */ - if (((state->refs && ! parsestate->err) - || state->state == GRUB_PARSER_STATE_ESC - || state->state == GRUB_PARSER_STATE_QUOTE - || state->state == GRUB_PARSER_STATE_DQUOTE) - && state->getline) - { - int doexit = 0; - if (state->state != GRUB_PARSER_STATE_ESC - && state->state != GRUB_PARSER_STATE_QUOTE - && state->state != GRUB_PARSER_STATE_DQUOTE - && ! state->was_newline) - { - state->was_newline = 1; - state->tokenonhold = '\n'; - break; - } - while (! state->script || ! *state->script) - { - grub_free (state->newscript); - state->newscript = 0; - state->getline (&state->newscript, 1); - state->script = state->newscript; - if (! state->script) - { - doexit = 1; - break; - } - } - if (doexit) - break; - grub_dprintf ("scripting", "token=`\\n'\n"); - recordchar (state, '\n'); - if (state->state == GRUB_PARSER_STATE_VARNAME) - state->state = GRUB_PARSER_STATE_TEXT; - if (state->state == GRUB_PARSER_STATE_QVARNAME) - state->state = GRUB_PARSER_STATE_DQUOTE; - if (state->state == GRUB_PARSER_STATE_DQUOTE - || state->state == GRUB_PARSER_STATE_QUOTE) - yylval->arg = grub_script_arg_add (parsestate, yylval->arg, - GRUB_SCRIPT_ARG_TYPE_STR, - "\n"); - } - else - { - grub_free (state->newscript); - state->newscript = 0; - state->done = 1; - grub_dprintf ("scripting", "token=`\\n'\n"); - state->tokenonhold = '\n'; - break; - } - } - state->was_newline = 0; - - newstate = grub_parser_cmdline_state (state->state, *state->script, &use); - - /* Check if it is a text. */ - if (check_textstate (newstate)) - { - char *buffer = NULL; - int bufpos = 0; - /* Buffer is initially large enough to hold most commands - but extends automatically when needed. */ - int bufsize = 128; - - buffer = grub_malloc (bufsize); - - /* In case the string is not quoted, this can be a one char - length symbol. */ - if (newstate == GRUB_PARSER_STATE_TEXT) - { - int doexit = 0; - switch (*state->script) - { - case ' ': - while (*state->script) - { - newstate = grub_parser_cmdline_state (state->state, - *state->script, &use); - if (! (state->state == GRUB_PARSER_STATE_TEXT - && *state->script == ' ')) - { - grub_dprintf ("scripting", "token=` '\n"); - if (! firstrun) - doexit = 1; - break; - } - state->state = newstate; - nextchar (state); - } - grub_dprintf ("scripting", "token=` '\n"); - if (! firstrun) - doexit = 1; - break; - case '{': - case '}': - case ';': - case '\n': - { - char c; - grub_dprintf ("scripting", "token=`%c'\n", *state->script); - c = *state->script; - nextchar (state); - state->tokenonhold = c; - doexit = 1; - break; - } - } - if (doexit) - { - grub_free (buffer); - break; - } - } - - /* Read one token, possible quoted. */ - while (*state->script) - { - newstate = grub_parser_cmdline_state (state->state, - *state->script, &use); - - /* Check if a variable name starts. */ - if (check_varstate (newstate)) - break; - - /* If the string is not quoted or escaped, stop processing - when a special token was found. It will be recognized - next time when this function is called. */ - if (newstate == GRUB_PARSER_STATE_TEXT - && state->state != GRUB_PARSER_STATE_ESC - && state->state != GRUB_PARSER_STATE_QUOTE - && state->state != GRUB_PARSER_STATE_DQUOTE) - { - int breakout = 0; - - switch (use) - { - case ' ': - case '{': - case '}': - case ';': - case '\n': - breakout = 1; - } - if (breakout) - break; - } - - if (use) - { - if (bufsize <= bufpos + 1) - { - bufsize <<= 1; - buffer = grub_realloc (buffer, bufsize); - } - buffer[bufpos++] = use; - } - - state->state = newstate; - nextchar (state); - } - - /* A string of text was read in. */ - if (bufsize <= bufpos + 1) - { - bufsize <<= 1; - buffer = grub_realloc (buffer, bufsize); - } - - buffer[bufpos++] = 0; - - grub_dprintf ("scripting", "token=`%s'\n", buffer); - yylval->arg = grub_script_arg_add (parsestate, yylval->arg, - GRUB_SCRIPT_ARG_TYPE_STR, buffer); - - grub_free (buffer); - } - else if (newstate == GRUB_PARSER_STATE_VAR - || newstate == GRUB_PARSER_STATE_QVAR) - { - char *buffer = NULL; - int bufpos = 0; - /* Buffer is initially large enough to hold most commands - but extends automatically when needed. */ - int bufsize = 128; - - buffer = grub_malloc (bufsize); - - /* This is a variable, read the variable name. */ - while (*state->script) - { - newstate = grub_parser_cmdline_state (state->state, - *state->script, &use); - - /* Check if this character is not part of the variable name - anymore. */ - if (! (check_varstate (newstate))) - { - if (state->state == GRUB_PARSER_STATE_VARNAME2 - || state->state == GRUB_PARSER_STATE_QVARNAME2) - nextchar (state); - state->state = newstate; - break; - } - - if (use) - { - if (bufsize <= bufpos + 1) - { - bufsize <<= 1; - buffer = grub_realloc (buffer, bufsize); - } - buffer[bufpos++] = use; - } - - nextchar (state); - state->state = newstate; - } - - if (bufsize <= bufpos + 1) - { - bufsize <<= 1; - buffer = grub_realloc (buffer, bufsize); - } - - buffer[bufpos++] = 0; - - state->state = newstate; - yylval->arg = grub_script_arg_add (parsestate, yylval->arg, - GRUB_SCRIPT_ARG_TYPE_VAR, buffer); - grub_dprintf ("scripting", "vartoken=`%s'\n", buffer); - - grub_free (buffer); + lexer->record = 0; + grub_script_yyerror (parser, "out of memory"); + return; + } + } + grub_strcpy (lexer->recording + lexer->recordpos, str); + lexer->recordpos += len; +} + +/* Append '\n' to SRC, before '\0' */ +static char * +append_newline (const char *src) +{ + char *line; + grub_size_t len; + + len = grub_strlen (src); + line = grub_malloc (len + 1); + if (!line) + return 0; + + grub_strcpy (line, src); + + line[len] = '\n'; + line[len + 1] = '\0'; + return line; +} + +/* Read next line of input if necessary, and set yyscanner buffers. */ +int +grub_script_lexer_yywrap (struct grub_parser_param *parserstate) +{ + int len; + char *line; + char *line2; + YY_BUFFER_STATE buffer; + struct grub_lexer_param *lexerstate = parserstate->lexerstate; + + if (!lexerstate->refs) + return 0; + + if (!lexerstate->getline) + { + grub_script_yyerror (parserstate, "unexpected end of file"); + return 0; + } + + line = 0; + buffer = 0; + lexerstate->getline (&line, 1); + if (!line) + { + grub_script_yyerror (parserstate, "input error"); /* XXX this could be for ^C case? */ + return 0; + } + + len = grub_strlen (line); + if (line[len - 1] == '\n') + { + buffer = yy_scan_string (line, lexerstate->yyscanner); + } + else + { + line2 = append_newline (line); + if (line2) + { + buffer = yy_scan_string (line2, lexerstate->yyscanner); + grub_free (line2); + } + } + + grub_free (line); + if (!buffer) + { + grub_script_yyerror (parserstate, "out of memory"); + return 0; + } + + return 1; +} + +struct grub_lexer_param * +grub_script_lexer_init (struct grub_parser_param *parser, char *script, + grub_reader_getline_t getline) +{ + int len; + char *script2; + YY_BUFFER_STATE buffer; + struct grub_lexer_param *lexerstate; + + lexerstate = grub_zalloc (sizeof (*lexerstate)); + if (!lexerstate) + return 0; + + lexerstate->text = grub_malloc (GRUB_LEXER_TOKEN_MAX); + if (!lexerstate->text) + { + grub_free (lexerstate); + return 0; + } + + lexerstate->getline = getline; /* rest are all zeros already */ + if (yylex_init (&lexerstate->yyscanner)) + { + grub_free (lexerstate->text); + grub_free (lexerstate); + return 0; + } + + buffer = 0; + script = script ? : "\n"; + len = grub_strlen (script); + + if (script[len - 1] == '\n') + { + buffer = yy_scan_string (script, lexerstate->yyscanner); + } + else + { + script2 = append_newline (script); + if (script2) + { + buffer = yy_scan_string (script2, lexerstate->yyscanner); + grub_free (script2); + } + } + + if (!buffer) + { + yylex_destroy (lexerstate->yyscanner); + grub_free (lexerstate->yyscanner); + + grub_free (lexerstate->text); + grub_free (lexerstate); + return 0; + } + yyset_extra (parser, lexerstate->yyscanner); + + return lexerstate; +} + +void +grub_script_lexer_fini (struct grub_lexer_param *lexerstate) +{ + if (!lexerstate) + return; + + yylex_destroy (lexerstate->yyscanner); + + grub_free (lexerstate->recording); + grub_free (lexerstate->text); + grub_free (lexerstate); +} + +int +grub_script_yylex (union YYSTYPE *value, + struct grub_parser_param *parserstate) +{ + char *str; + int token; + grub_script_arg_type_t type; + struct grub_lexer_param *lexerstate = parserstate->lexerstate; + + value->arg = 0; + if (parserstate->err) + return GRUB_PARSER_TOKEN_BAD; + + if (lexerstate->eof) + return GRUB_PARSER_TOKEN_EOF; + + /* + * Words with environment variables, like foo${bar}baz needs + * multiple tokens to be merged into a single grub_script_arg. We + * use two variables to achieve this: lexerstate->merge_start and + * lexerstate->merge_end + */ + + lexerstate->merge_start = 0; + lexerstate->merge_end = 0; + do + { + /* Empty lexerstate->text. */ + lexerstate->size = 0; + lexerstate->text[0] = '\0'; + + token = yylex (value, lexerstate->yyscanner); + if (token == GRUB_PARSER_TOKEN_BAD) + break; + + /* Merging feature uses lexerstate->text instead of yytext. */ + if (lexerstate->merge_start) + { + lexerstate->text[lexerstate->size] = '\0'; + str = lexerstate->text; + type = (token == GRUB_PARSER_TOKEN_NAME ? + GRUB_SCRIPT_ARG_TYPE_VAR : GRUB_SCRIPT_ARG_TYPE_STR); } else { - /* There is either text or a variable name. In the case you - arrive here there is a serious problem with the lexer. */ - grub_error (GRUB_ERR_BAD_ARGUMENT, "Internal error\n"); - return 0; + str = yyget_text (lexerstate->yyscanner); + type = GRUB_SCRIPT_ARG_TYPE_STR; } - } - - if (yylval->arg == 0) - { - int token = state->tokenonhold; - state->tokenonhold = 0; - return token; - } - - if (yylval->arg->next == 0 && yylval->arg->type == GRUB_SCRIPT_ARG_TYPE_STR) - { - /* Detect some special tokens. */ - if (! grub_strcmp (yylval->arg->str, "while")) - return GRUB_PARSER_TOKEN_WHILE; - else if (! grub_strcmp (yylval->arg->str, "if")) - return GRUB_PARSER_TOKEN_IF; - else if (! grub_strcmp (yylval->arg->str, "function")) - return GRUB_PARSER_TOKEN_FUNCTION; - else if (! grub_strcmp (yylval->arg->str, "menuentry")) - return GRUB_PARSER_TOKEN_MENUENTRY; - else if (! grub_strcmp (yylval->arg->str, "@")) - return GRUB_PARSER_TOKEN_MENUENTRY; - else if (! grub_strcmp (yylval->arg->str, "else")) - return GRUB_PARSER_TOKEN_ELSE; - else if (! grub_strcmp (yylval->arg->str, "then")) - return GRUB_PARSER_TOKEN_THEN; - else if (! grub_strcmp (yylval->arg->str, "fi")) - return GRUB_PARSER_TOKEN_FI; - } - - return GRUB_PARSER_TOKEN_ARG; + /* grub_printf ("tok %u, txt [%s] size %u\n", token, str, lexerstate->size); */ + + value->arg = grub_script_arg_add (parserstate, value->arg, type, str); + } + while (lexerstate->merge_start && !lexerstate->merge_end); + + if (!value->arg || parserstate->err) + return GRUB_PARSER_TOKEN_BAD; + + return token; } void -grub_script_yyerror (struct grub_parser_param *lex __attribute__ ((unused)), - char const *err) +grub_script_yyerror (struct grub_parser_param *state, char const *err) { grub_printf ("%s\n", err); + state->err++; } === modified file 'script/parser.y' --- old/script/parser.y 2009-11-23 15:37:33 +0000 +++ new/script/parser.y 2009-12-20 08:28:15 +0000 @@ -21,10 +21,10 @@ #include #include -#define YYFREE grub_free -#define YYMALLOC grub_malloc +#define YYFREE grub_free +#define YYMALLOC grub_malloc #define YYLTYPE_IS_TRIVIAL 0 -#define YYENABLE_NLS 0 +#define YYENABLE_NLS 0 %} @@ -35,163 +35,217 @@ char *string; } -%token GRUB_PARSER_TOKEN_IF "if" -%token GRUB_PARSER_TOKEN_WHILE "while" -%token GRUB_PARSER_TOKEN_FUNCTION "function" -%token GRUB_PARSER_TOKEN_MENUENTRY "menuentry" -%token GRUB_PARSER_TOKEN_ELSE "else" -%token GRUB_PARSER_TOKEN_THEN "then" -%token GRUB_PARSER_TOKEN_FI "fi" -%token GRUB_PARSER_TOKEN_ARG -%type script_init script grubcmd command commands commandblock menuentry if -%type arguments; -%type GRUB_PARSER_TOKEN_ARG; +%token GRUB_PARSER_TOKEN_BAD +%token GRUB_PARSER_TOKEN_EOF 0 "end-of-input" + +%token GRUB_PARSER_TOKEN_NEWLINE "\n" +%token GRUB_PARSER_TOKEN_AND "&&" +%token GRUB_PARSER_TOKEN_OR "||" +%token GRUB_PARSER_TOKEN_SEMI2 ";;" +%token GRUB_PARSER_TOKEN_PIPE "|" +%token GRUB_PARSER_TOKEN_AMP "&" +%token GRUB_PARSER_TOKEN_SEMI ";" +%token GRUB_PARSER_TOKEN_LPAR "(" +%token GRUB_PARSER_TOKEN_RPAR ")" +%token GRUB_PARSER_TOKEN_LBR "{" +%token GRUB_PARSER_TOKEN_RBR "}" +%token GRUB_PARSER_TOKEN_NOT "!" +%token GRUB_PARSER_TOKEN_LSQBR2 "[" +%token GRUB_PARSER_TOKEN_RSQBR2 "]" +%token GRUB_PARSER_TOKEN_LT "<" +%token GRUB_PARSER_TOKEN_GT ">" + +%token GRUB_PARSER_TOKEN_CASE "case" +%token GRUB_PARSER_TOKEN_DO "do" +%token GRUB_PARSER_TOKEN_DONE "done" +%token GRUB_PARSER_TOKEN_ELIF "elif" +%token GRUB_PARSER_TOKEN_ELSE "else" +%token GRUB_PARSER_TOKEN_ESAC "esac" +%token GRUB_PARSER_TOKEN_FI "fi" +%token GRUB_PARSER_TOKEN_FOR "for" +%token GRUB_PARSER_TOKEN_IF "if" +%token GRUB_PARSER_TOKEN_IN "in" +%token GRUB_PARSER_TOKEN_SELECT "select" +%token GRUB_PARSER_TOKEN_THEN "then" +%token GRUB_PARSER_TOKEN_UNTIL "until" +%token GRUB_PARSER_TOKEN_WHILE "while" +%token GRUB_PARSER_TOKEN_TIME "time" +%token GRUB_PARSER_TOKEN_FUNCTION "function" +%token GRUB_PARSER_TOKEN_MENUENTRY "menuentry" +%token GRUB_PARSER_TOKEN_NAME "name" +%token GRUB_PARSER_TOKEN_WORD "word" + +%type word argument arguments0 arguments1 +%type script_init script grubcmd ifcmd command +%type commands1 menuentry statement %pure-parser -%lex-param { struct grub_parser_param *state }; +%error-verbose + +%lex-param { struct grub_parser_param *state }; %parse-param { struct grub_parser_param *state }; +%start script_init + %% /* It should be possible to do this in a clean way... */ -script_init: { state->err = 0; } script - { - state->parsed = $2; - } -; - -script: { $$ = 0; } - | '\n' { $$ = 0; } - | commands { $$ = $1; } - | function '\n' { $$ = 0; } - | menuentry '\n' { $$ = $1; } - | error - { - $$ = 0; - yyerror (state, "Incorrect command"); - state->err = 1; - yyerrok; - } -; - -delimiter: '\n' - | ';' - | delimiter '\n' -; - -newlines: /* Empty */ - | newlines '\n' -; - - - -arguments: GRUB_PARSER_TOKEN_ARG - { - $$ = grub_script_add_arglist (state, 0, $1); - } - | arguments GRUB_PARSER_TOKEN_ARG - { - $$ = grub_script_add_arglist (state, $1, $2); - } -; - -grubcmd: arguments - { - $$ = grub_script_create_cmdline (state, $1); - } +script_init: { state->err = 0; } script { state->parsed = $2; state->err = 0; } +; + +script: /* Empty */ + { + $$ = 0; + } + | "\n" script + { + $$ = 0; + } + | statement delimiter script + { + struct grub_script_cmdblock *cmd; + cmd = (struct grub_script_cmdblock *) $3; + $$ = grub_script_add_cmd (state, cmd, $1); + } + | error + { + $$ = 0; + yyerror (state, "Incorrect command"); + yyerrok; + } +; + +newlines0: /* Empty */ | newlines1 ; +newlines1: newlines0 "\n" ; + +delimiter: ";" + | "\n" +; +delimiters0: /* Empty */ | delimiters1 ; +delimiters1: delimiter + | delimiters1 "\n" +; + +word: "name" { $$ = grub_script_add_arglist (state, 0, $1); } + | "word" { $$ = grub_script_add_arglist (state, 0, $1); } +; + +statement: command { $$ = $1; } + | function { $$ = 0; } + | menuentry { $$ = $1; } + +argument : "case" { $$ = grub_script_add_arglist (state, 0, $1); } + | "do" { $$ = grub_script_add_arglist (state, 0, $1); } + | "done" { $$ = grub_script_add_arglist (state, 0, $1); } + | "elif" { $$ = grub_script_add_arglist (state, 0, $1); } + | "else" { $$ = grub_script_add_arglist (state, 0, $1); } + | "esac" { $$ = grub_script_add_arglist (state, 0, $1); } + | "fi" { $$ = grub_script_add_arglist (state, 0, $1); } + | "for" { $$ = grub_script_add_arglist (state, 0, $1); } + | "if" { $$ = grub_script_add_arglist (state, 0, $1); } + | "in" { $$ = grub_script_add_arglist (state, 0, $1); } + | "select" { $$ = grub_script_add_arglist (state, 0, $1); } + | "then" { $$ = grub_script_add_arglist (state, 0, $1); } + | "until" { $$ = grub_script_add_arglist (state, 0, $1); } + | "while" { $$ = grub_script_add_arglist (state, 0, $1); } + | "function" { $$ = grub_script_add_arglist (state, 0, $1); } + | "menuentry" { $$ = grub_script_add_arglist (state, 0, $1); } + | word { $$ = $1; } +; + +arguments0: /* Empty */ { $$ = 0; } + | arguments1 { $$ = $1; } +; +arguments1: argument arguments0 + { + if ($2) + { + $1->next = $2; + $1->argcount += $2->argcount; + $2->argcount = 0; + } + $$ = $1; + } +; + +grubcmd: word arguments0 + { + if ($2) { + $1->next = $2; + $1->argcount += $2->argcount; + $2->argcount = 0; + } + $$ = grub_script_create_cmdline (state, $1); + } ; /* A single command. */ -command: grubcmd delimiter { $$ = $1; } - | if delimiter { $$ = $1; } - | commandblock delimiter { $$ = $1; } -; - -/* A block of commands. */ -commands: command - { - $$ = grub_script_add_cmd (state, 0, $1); - } - | command commands - { - struct grub_script_cmdblock *cmd; - cmd = (struct grub_script_cmdblock *) $2; - $$ = grub_script_add_cmd (state, cmd, $1); - } -; - -/* A function. Carefully save the memory that is allocated. Don't - change any stuff because it might seem like a fun thing to do! - Special care was take to make sure the mid-rule actions are - executed on the right moment. So the `commands' rule should be - recognized after executing the `grub_script_mem_record; and before - `grub_script_mem_record_stop'. */ -function: "function" GRUB_PARSER_TOKEN_ARG - { - grub_script_lexer_ref (state->lexerstate); - } newlines '{' - { - /* The first part of the function was recognized. - Now start recording the memory usage to store - this function. */ - state->func_mem = grub_script_mem_record (state); - } newlines commands '}' - { - struct grub_script *script; - - /* All the memory usage for parsing this function - was recorded. */ - state->func_mem = grub_script_mem_record_stop (state, - state->func_mem); - script = grub_script_create ($8, state->func_mem); - if (script) - grub_script_function_create ($2, script); - grub_script_lexer_deref (state->lexerstate); - } -; - -/* Carefully designed, together with `menuentry' so everything happens - just in the expected order. */ -commandblock: '{' - { - grub_script_lexer_ref (state->lexerstate); - } - newlines commands '}' - { - grub_script_lexer_deref (state->lexerstate); - $$ = $4; - } -; - -/* A menu entry. Carefully save the memory that is allocated. */ -menuentry: "menuentry" - { - grub_script_lexer_ref (state->lexerstate); - } arguments newlines '{' - { - grub_script_lexer_record_start (state->lexerstate); - } newlines commands '}' - { - char *menu_entry; - menu_entry = grub_script_lexer_record_stop (state->lexerstate); - grub_script_lexer_deref (state->lexerstate); - $$ = grub_script_create_cmdmenu (state, $3, menu_entry, 0); - } -; - -/* The first part of the if statement. It's used to switch the lexer - to a state in which it demands more tokens. */ -if_statement: "if" { grub_script_lexer_ref (state->lexerstate); } -; - -/* The if statement. */ -if: if_statement commands "then" newlines commands "fi" - { - $$ = grub_script_create_cmdif (state, $2, $5, 0); - grub_script_lexer_deref (state->lexerstate); - } - | if_statement commands "then" newlines commands "else" newlines commands "fi" - { - $$ = grub_script_create_cmdif (state, $2, $5, $8); - grub_script_lexer_deref (state->lexerstate); - } +command: grubcmd { $$ = $1; } + | ifcmd { $$ = $1; } +; + +/* A list of commands. */ +commands1: newlines0 command + { + $$ = grub_script_add_cmd (state, 0, $2); + } + | commands1 delimiters1 command + { + struct grub_script_cmd *cmd; + struct grub_script_cmdblock *cmdblock; + + cmdblock = (struct grub_script_cmdblock *) $1; + cmd = cmdblock->cmdlist; + while (cmd->next) + cmd = cmd->next; + cmd->next = $3; + } +; + +function: "function" "name" + { + grub_script_lexer_ref (state->lexerstate); + state->func_mem = grub_script_mem_record (state); + } + delimiters0 "{" commands1 delimiters1 "}" + { + struct grub_script *script; + state->func_mem = grub_script_mem_record_stop (state, + state->func_mem); + script = grub_script_create ($6, state->func_mem); + if (script) + grub_script_function_create ($2, script); + + grub_script_lexer_deref (state->lexerstate); + } +; + +menuentry: "menuentry" + { + grub_script_lexer_ref (state->lexerstate); + } + arguments1 + { + grub_script_lexer_record_start (state); + } + delimiters0 "{" commands1 delimiters1 "}" + { + char *menu_entry; + menu_entry = grub_script_lexer_record_stop (state); + grub_script_lexer_deref (state->lexerstate); + $$ = grub_script_create_cmdmenu (state, $3, menu_entry, 0); + } +; + +if: "if" { grub_script_lexer_ref (state->lexerstate); } +; +ifcmd: if commands1 delimiters1 "then" commands1 delimiters1 "fi" + { + $$ = grub_script_create_cmdif (state, $2, $5, 0); + grub_script_lexer_deref (state->lexerstate); + } + | if commands1 delimiters1 "then" commands1 delimiters1 "else" commands1 delimiters1 "fi" + { + $$ = grub_script_create_cmdif (state, $2, $5, $8); + grub_script_lexer_deref (state->lexerstate); + } ; === modified file 'script/script.c' --- old/script/script.c 2009-11-23 15:37:33 +0000 +++ new/script/script.c 2009-12-20 08:28:15 +0000 @@ -94,32 +94,34 @@ void grub_script_free (struct grub_script *script) { - if (! script) + if (!script) return; grub_script_mem_free (script->mem); grub_free (script); } - + /* Extend the argument arg with a variable or string of text. If ARG is zero a new list is created. */ struct grub_script_arg * -grub_script_arg_add (struct grub_parser_param *state, struct grub_script_arg *arg, - grub_script_arg_type_t type, char *str) +grub_script_arg_add (struct grub_parser_param *state, + struct grub_script_arg *arg, grub_script_arg_type_t type, + char *str) { struct grub_script_arg *argpart; struct grub_script_arg *ll; int len; - argpart = (struct grub_script_arg *) grub_script_malloc (state, sizeof (*arg)); + argpart = + (struct grub_script_arg *) grub_script_malloc (state, sizeof (*arg)); argpart->type = type; len = grub_strlen (str) + 1; argpart->str = grub_script_malloc (state, len); grub_memcpy (argpart->str, str, len); argpart->next = 0; - if (! arg) + if (!arg) return argpart; for (ll = arg; ll->next; ll = ll->next); @@ -132,19 +134,21 @@ is zero, a new list will be created. */ struct grub_script_arglist * grub_script_add_arglist (struct grub_parser_param *state, - struct grub_script_arglist *list, struct grub_script_arg *arg) + struct grub_script_arglist *list, + struct grub_script_arg *arg) { struct grub_script_arglist *link; struct grub_script_arglist *ll; grub_dprintf ("scripting", "arglist\n"); - link = (struct grub_script_arglist *) grub_script_malloc (state, sizeof (*link)); + link = + (struct grub_script_arglist *) grub_script_malloc (state, sizeof (*link)); link->next = 0; link->arg = arg; link->argcount = 0; - if (! list) + if (!list) { link->argcount++; return link; @@ -209,8 +213,7 @@ struct grub_script_cmd * grub_script_create_cmdmenu (struct grub_parser_param *state, struct grub_script_arglist *arglist, - char *sourcecode, - int options) + char *sourcecode, int options) { struct grub_script_cmd_menuentry *cmd; int i; @@ -250,13 +253,14 @@ { grub_dprintf ("scripting", "cmdblock\n"); - if (! cmd) + if (!cmd) return (struct grub_script_cmd *) cmdblock; - if (! cmdblock) + if (!cmdblock) { cmdblock = (struct grub_script_cmdblock *) grub_script_malloc (state, - sizeof (*cmdblock)); + sizeof + (*cmdblock)); cmdblock->cmd.exec = grub_script_execute_cmdblock; cmdblock->cmd.next = 0; cmdblock->cmdlist = cmd; @@ -270,16 +274,16 @@ return (struct grub_script_cmd *) cmdblock; } - + struct grub_script * grub_script_create (struct grub_script_cmd *cmd, struct grub_script_mem *mem) { struct grub_script *parsed; parsed = grub_malloc (sizeof (*parsed)); - if (! parsed) + if (!parsed) { grub_script_mem_free (mem); grub_free (cmd); @@ -304,16 +308,16 @@ struct grub_parser_param *parsestate; parsed = grub_malloc (sizeof (*parsed)); - if (! parsed) + if (!parsed) return 0; parsestate = grub_zalloc (sizeof (*parsestate)); - if (! parsestate) + if (!parsestate) return 0; /* Initialize the lexer. */ - lexstate = grub_script_lexer_init (script, getline); - if (! lexstate) + lexstate = grub_script_lexer_init (parsestate, script, getline); + if (!lexstate) { grub_free (parsed); grub_free (parsestate); @@ -330,7 +334,7 @@ struct grub_script_mem *memfree; memfree = grub_script_mem_record_stop (parsestate, membackup); grub_script_mem_free (memfree); - grub_free (lexstate); + grub_script_lexer_fini (lexstate); grub_free (parsestate); return 0; } @@ -338,7 +342,7 @@ parsed->mem = grub_script_mem_record_stop (parsestate, membackup); parsed->cmd = parsestate->parsed; - grub_free (lexstate); + grub_script_lexer_fini (lexstate); grub_free (parsestate); return parsed; === added file 'script/yylex.l' --- old/script/yylex.l 1970-01-01 00:00:00 +0000 +++ new/script/yylex.l 2009-12-20 08:28:15 +0000 @@ -0,0 +1,281 @@ +%{ + +#include +#include +#include +#include +#include "grub_script.tab.h" + +#define yyfree grub_lexer_yyfree +#define yyalloc grub_lexer_yyalloc +#define yyrealloc grub_lexer_yyrealloc + +/* + * XXX As we don't have access to yyscanner, we cannot do much except + * to print the fatal error. + */ +#define YY_FATAL_ERROR(msg) \ + do { \ + grub_printf ("fatal error: %s\n", msg); \ + } while (0) + +#define PUSH(c) \ + do { \ + if (yyextra->lexerstate->size >= GRUB_LEXER_TOKEN_MAX - 1) \ + grub_script_yyerror (yyextra, "token too long"); \ + else \ + yyextra->lexerstate->text[yyextra->lexerstate->size++] = c; \ + } while (0) + +#define COPY(str) \ + do { \ + char *ptr = str; \ + while (*ptr && ! yyextra->err) \ + { \ + PUSH (*ptr); \ + ptr++; \ + } \ + } while (0) + +#define RECORD \ + do { \ + grub_script_lexer_record (yyextra, yytext); \ + } while (0) + +/* We don't need YY_INPUT, as we rely on yy_scan_strings */ +#define YY_INPUT(buf,res,max) do { res = 0; } while (0) + +/* forward declarations */ +static void grub_lexer_yyfree (void *, yyscan_t yyscanner); +static void* grub_lexer_yyalloc (yy_size_t, yyscan_t yyscanner); +static void* grub_lexer_yyrealloc (void*, yy_size_t, yyscan_t yyscanner); + +%} + +%top{ + +/* + * Some flex hacks for -nostdinc; XXX We need to fix these when libc + * support becomes availble in GRUB. + */ + +#include + +typedef grub_size_t size_t; +typedef grub_size_t yy_size_t; +#define YY_TYPEDEF_YY_SIZE_T 1 + +#define FILE void +#define stdin 0 +#define stdout 0 +#define EOF 0 + +#define errno grub_errno +#define EINVAL GRUB_ERR_BAD_NUMBER +#define ENOMEM GRUB_ERR_OUT_OF_MEMORY + +#define strlen grub_strlen +#define memset grub_memset + +#define fprintf(...) 0 +#define exit(...) + +#pragma GCC diagnostic warning "-Wunused-variable" +#pragma GCC diagnostic warning "-Wunused-function" +#pragma GCC diagnostic warning "-Wunused-parameter" + +} + +%option ecs +%option meta-ecs + +%option warn +%option array +%option stack +%option reentrant +%option bison-bridge +%option never-interactive + +%option noyyfree noyyalloc noyyrealloc +%option nounistd nostdinit nodefault noyylineno noyywrap + +/* Reduce lexer size, by not defining these. */ +%option noyy_top_state +%option noinput nounput +%option noyyget_in noyyset_in +%option noyyget_out noyyset_out +%option noyyget_debug noyyset_debug +%option noyyget_lineno noyyset_lineno + +%option extra-type="struct grub_parser_param*" + +BLANK [ \t] +COMMENT ^[ \t]*#.*$ + +CHAR [^|&;()<> \t\n\'\"] +NAME [[:alpha:]_][[:alnum:][:digit:]_]* + +ESC \\. +VARIABLE ${NAME}|$\{{NAME}\} +DQSTR \"([^\"]|\\\")*\" +SQSTR \'[^\']*\' +WORD ({CHAR}|{DQSTR}|{SQSTR}|{ESC}|{VARIABLE})+ + +%x SPLIT +%x DQUOTE +%x SQUOTE +%x VAR + +%% + + /* White spaces */ +{BLANK}+ { RECORD; } +{COMMENT} { RECORD; } + + /* Special symbols */ +"\n" { RECORD; return GRUB_PARSER_TOKEN_NEWLINE; } +"||" { RECORD; return GRUB_PARSER_TOKEN_OR; } +"&&" { RECORD; return GRUB_PARSER_TOKEN_AND; } +";;" { RECORD; return GRUB_PARSER_TOKEN_SEMI2; } +"|" { RECORD; return GRUB_PARSER_TOKEN_PIPE; } +"&" { RECORD; return GRUB_PARSER_TOKEN_AMP; } +";" { RECORD; return GRUB_PARSER_TOKEN_SEMI; } +"(" { RECORD; return GRUB_PARSER_TOKEN_LPAR; } +")" { RECORD; return GRUB_PARSER_TOKEN_RPAR; } +"<" { RECORD; return GRUB_PARSER_TOKEN_LT; } +">" { RECORD; return GRUB_PARSER_TOKEN_GT; } + + /* Reserved words */ +"!" { RECORD; return GRUB_PARSER_TOKEN_NOT; } +"{" { RECORD; return GRUB_PARSER_TOKEN_LBR; } +"}" { RECORD; return GRUB_PARSER_TOKEN_RBR; } +"[[" { RECORD; return GRUB_PARSER_TOKEN_RSQBR2; } +"]]" { RECORD; return GRUB_PARSER_TOKEN_LSQBR2; } +"time" { RECORD; return GRUB_PARSER_TOKEN_TIME; } +"case" { RECORD; return GRUB_PARSER_TOKEN_CASE; } +"do" { RECORD; return GRUB_PARSER_TOKEN_DO; } +"done" { RECORD; return GRUB_PARSER_TOKEN_DONE; } +"elif" { RECORD; return GRUB_PARSER_TOKEN_ELIF; } +"else" { RECORD; return GRUB_PARSER_TOKEN_ELSE; } +"esac" { RECORD; return GRUB_PARSER_TOKEN_ESAC; } +"fi" { RECORD; return GRUB_PARSER_TOKEN_FI; } +"for" { RECORD; return GRUB_PARSER_TOKEN_FOR; } +"if" { RECORD; return GRUB_PARSER_TOKEN_IF; } +"in" { RECORD; return GRUB_PARSER_TOKEN_IN; } +"select" { RECORD; return GRUB_PARSER_TOKEN_SELECT; } +"then" { RECORD; return GRUB_PARSER_TOKEN_THEN; } +"until" { RECORD; return GRUB_PARSER_TOKEN_UNTIL; } +"while" { RECORD; return GRUB_PARSER_TOKEN_WHILE; } +"function" { RECORD; return GRUB_PARSER_TOKEN_FUNCTION; } +"menuentry" { RECORD; return GRUB_PARSER_TOKEN_MENUENTRY; } + +{NAME} { RECORD; return GRUB_PARSER_TOKEN_NAME; } +{WORD} { + RECORD; + /* resplit yytext */ + yypush_buffer_state (YY_CURRENT_BUFFER, yyscanner); + if (yy_scan_string (yytext, yyscanner)) + { + yyextra->lexerstate->merge_start = 1; + yy_push_state (SPLIT, yyscanner); + } + else + { + grub_script_yyerror (yyextra, "out of memory"); + yypop_buffer_state (yyscanner); + return GRUB_PARSER_TOKEN_WORD; + } + } + +.|\n { + grub_script_yyerror (yyextra, "unrecognized token"); + return GRUB_PARSER_TOKEN_BAD; + } + + + /* Split word into multiple args */ + +{ + \\. { PUSH (yytext[1]); } + \" { yy_push_state (DQUOTE, yyscanner); } + \' { yy_push_state (SQUOTE, yyscanner); } + \$ { + yy_push_state (VAR, yyscanner); + return GRUB_PARSER_TOKEN_WORD; + } + {CHAR} { PUSH (yytext[0]); } + .|\n { + grub_script_yyerror (yyextra, "unrecognized token"); + return GRUB_PARSER_TOKEN_BAD; + } + + <> { + yy_pop_state (yyscanner); + yypop_buffer_state (yyscanner); + yyextra->lexerstate->merge_end = 1; + return GRUB_PARSER_TOKEN_WORD; + } +} + +{ + {NAME} { + COPY (yytext); + yy_pop_state (yyscanner); + return GRUB_PARSER_TOKEN_NAME; + } + \{{NAME}\} { + yytext[yyleng - 1] = '\0'; + COPY (yytext + 1); + yy_pop_state (yyscanner); + return GRUB_PARSER_TOKEN_NAME; + } + .|\n { return GRUB_PARSER_TOKEN_BAD; } +} + +{ + \' { yy_pop_state (yyscanner); } + (.|\n) { PUSH (yytext[0]); } +} + +{ + \" { yy_pop_state (yyscanner); } + \$ { + yy_push_state (VAR, yyscanner); + return GRUB_PARSER_TOKEN_WORD; + } + \\\\ { PUSH ('\\'); } + \\\" { PUSH ('\"'); } + \\\n { /* ignore */ } + (.|\n) { PUSH (yytext[0]); } +} + +<> { + yypop_buffer_state (yyscanner); + if (! grub_script_lexer_yywrap (yyextra)) + { + yyextra->lexerstate->eof = 1; + return GRUB_PARSER_TOKEN_EOF; + } + } + +%% + +static void +grub_lexer_yyfree (void *ptr, yyscan_t yyscanner __attribute__ ((unused))) +{ + grub_free(ptr); +} + +static void* +grub_lexer_yyalloc (yy_size_t size, yyscan_t yyscanner __attribute__ ((unused))) +{ + return grub_malloc (size); +} + +static void* +grub_lexer_yyrealloc (void *ptr, yy_size_t size, + yyscan_t yyscanner __attribute__ ((unused))) +{ + return grub_realloc (ptr, size); +} + === added directory 'tests' === added file 'tests/echo_keywords.in' --- old/tests/echo_keywords.in 1970-01-01 00:00:00 +0000 +++ new/tests/echo_keywords.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,3 @@ +#! @builddir@/grub-shell-tester + +echo if then else fi for do done === added file 'tests/example_functional_test.c' --- old/tests/example_functional_test.c 1970-01-01 00:00:00 +0000 +++ new/tests/example_functional_test.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,17 @@ +/* All tests need to include test.h for GRUB testing framework. */ +#include + +/* Functional test main method. */ +static void +example_test (void) +{ + /* Check if 1st argument is true and report with default error message. */ + grub_test_assert (1 == 1); + + /* Check if 1st argument is true and report with custom error message. */ + grub_test_assert (2 == 2, "2 equal 2 expected"); + grub_test_assert (2 == 3, "2 is not equal to %d", 3); +} + +/* Register example_test method as a functional test. */ +GRUB_FUNCTIONAL_TEST ("example_functional_test", example_test); === added file 'tests/example_scripted_test.in' --- old/tests/example_scripted_test.in 1970-01-01 00:00:00 +0000 +++ new/tests/example_scripted_test.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh -e + +true === added file 'tests/example_unit_test.c' --- old/tests/example_unit_test.c 1970-01-01 00:00:00 +0000 +++ new/tests/example_unit_test.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,20 @@ +/* Unit tests are normal programs, so they can include C library. */ +#include + +/* All tests need to include test.h for GRUB testing framework. */ +#include + +/* Unit test main method. */ +static void +example_test (void) +{ + /* Check if 1st argument is true and report with default error message. */ + grub_test_assert (1 == 1); + + /* Check if 1st argument is true and report with custom error message. */ + grub_test_assert (2 == 2, "2 equal 2 expected"); + grub_test_assert (2 == 3, "2 is not equal to %d", 3); +} + +/* Register example_test method as a unit test. */ +GRUB_UNIT_TEST ("example_unit_test", example_test); === added file 'tests/functional_test.c' --- old/tests/functional_test.c 1970-01-01 00:00:00 +0000 +++ new/tests/functional_test.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +void * +grub_test_malloc (grub_size_t size) +{ + return grub_malloc (size); +} + +void +grub_test_free (void *ptr) +{ + grub_free (ptr); +} + +int +grub_test_vsprintf (char *str, const char *fmt, va_list args) +{ + return grub_vsprintf (str, fmt, args); +} + +char * +grub_test_strdup (const char *str) +{ + return grub_strdup (str); +} + +int +grub_test_printf (const char *fmt, ...) +{ + int r; + va_list ap; + + va_start (ap, fmt); + r = grub_vprintf (fmt, ap); + va_end (ap); + + return r; +} + +static grub_err_t +grub_functional_test (struct grub_extcmd *cmd __attribute__ ((unused)), + int argc, char **args __attribute__ ((unused))) +{ + int i; + int status; + grub_test_t test; + + auto int print_name (grub_test_t test); + int print_name (grub_test_t test) + { + grub_printf ("%s\n", test->name); + return 0; + } + + if (!argc) + { + grub_list_iterate (GRUB_AS_LIST (grub_test_list), + (grub_list_hook_t) print_name); + return GRUB_ERR_NONE; + } + + status = 0; + for (i = 0; i < argc; i++) + { + test = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_test_list), + args[i]); + status = grub_test_run (test->name) ? : status; + } + + return status; +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT (functional_test) +{ + cmd = grub_register_extcmd ("functional_test", grub_functional_test, + GRUB_COMMAND_FLAG_CMDLINE, + "functional_test [name ...]", + "Run or list functional tests.", 0); +} + +GRUB_MOD_FINI (functional_test) +{ + grub_unregister_extcmd (cmd); +} === added file 'tests/gettext_1.in' --- old/tests/gettext_1.in 1970-01-01 00:00:00 +0000 +++ new/tests/gettext_1.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,4 @@ +#! @builddir@/grub-shell-tester --modules=gettext + +gettext "hello world" + === added file 'tests/grub_script_echo1.in' --- old/tests/grub_script_echo1.in 1970-01-01 00:00:00 +0000 +++ new/tests/grub_script_echo1.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,16 @@ +#! @builddir@/grub-shell-tester + +foo=bar +echo $foo ${foo} +echo "$foo" "${foo}" +echo '$foo' '${foo}' +echo a$foob a${foo}b +echo ab"cd"ef$foo'gh'ij${foo}kl\ mn\"op\'qr\$st\(uv\yz\) + +foo=c +bar=h +echo e"$foo"${bar}o +e"$foo"${bar}o hello world + +foo=echo +$foo 1234 === added file 'tests/grub_script_functions1.in' --- old/tests/grub_script_functions1.in 1970-01-01 00:00:00 +0000 +++ new/tests/grub_script_functions1.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,18 @@ +#! @builddir@/grub-shell-tester + +function foo { echo foo; }; foo +function foo2 +{ + echo foo2 +} +foo2 + +function bar { echo one; if true; then echo yes; else echo no; fi; echo bar; foo; }; bar +function bar +{ + foo; + foo2; + bar; +} + + === added file 'tests/grub_sprintf.c' --- old/tests/grub_sprintf.c 1970-01-01 00:00:00 +0000 +++ new/tests/grub_sprintf.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,162 @@ +#include +#include +#include +#include + +#include +#include + +#define TEST(type,fmt,val) \ + do { \ + int r1, r2; \ + char b1[1024]; \ + char b2[1024]; \ + \ + b1[0] = b2[0] = '\0'; \ + r1 = sprintf (b1, fmt, val); \ + r2 = grub_sprintf (b2, fmt, val); \ + \ + grub_test_assert (strcmp (b1, b2) == 0, \ + "for (\"%s\","type") " \ + "result should be (\"%s\",%d) " \ + "but got (\"%s\",%d)", \ + fmt, val, b1, r1, b2, r2); \ + \ + grub_test_assert (r1 == r2, \ + "for (\"%s\","type") " \ + "result should be (\"%s\",%d) " \ + "but got (\"%s\",%d)", \ + fmt, val, b1, r1, b2, r2); \ + } while (0) + +#define D(fmt,v) TEST("%d",fmt,v) +#define U(fmt,v) TEST("%u",fmt,v) +#define x(fmt,v) TEST("%x",fmt,v) +#define X(fmt,v) TEST("%X",fmt,v) +#define P(fmt,v) TEST("%p",fmt,v) +#define S(fmt,s) TEST("%s",fmt,s) + +static void +sprintf_checks (void) +{ + D ("%d", -1); + D ("%d", 0); + D ("%d", 1); + D ("%5d", -1); + D ("%5d", 0); + D ("%5d", 1); + D ("%-5d", -1); + D ("%-5d", 0); + D ("%-5d", 1); + D ("%.5d", -1); + D ("%.5d", 0); + D ("%.5d", 1); + D ("%5.0d", -1); + D ("%5.0d", 0); + D ("%5.0d", 1); + D ("%-5.0d", -1); + D ("%-5.0d", 0); + D ("%-5.0d", 1); + + U ("%d", -1); + U ("%d", 0); + U ("%d", 1); + U ("%5d", -1); + U ("%5d", 0); + U ("%5d", 1); + U ("%-5d", -1); + U ("%-5d", 0); + U ("%-5d", 1); + U ("%.5d", -1); + U ("%.5d", 0); + U ("%.5d", 1); + U ("%5.0d", -1); + U ("%5.0d", 0); + U ("%5.0d", 1); + U ("%-5.0d", -1); + U ("%-5.0d", 0); + U ("%-5.0d", 1); + + x ("%d", -1); + x ("%d", 0); + x ("%d", 1); + x ("%5d", -1); + x ("%5d", 0); + x ("%5d", 1); + x ("%-5d", -1); + x ("%-5d", 0); + x ("%-5d", 1); + x ("%.5d", -1); + x ("%.5d", 0); + x ("%.5d", 1); + x ("%5.0d", -1); + x ("%5.0d", 0); + x ("%5.0d", 1); + x ("%-5.0d", -1); + x ("%-5.0d", 0); + x ("%-5.0d", 1); + + X ("%d", -1); + X ("%d", 0); + X ("%d", 1); + X ("%5d", -1); + X ("%5d", 0); + X ("%5d", 1); + X ("%-5d", -1); + X ("%-5d", 0); + X ("%-5d", 1); + X ("%.5d", -1); + X ("%.5d", 0); + X ("%.5d", 1); + X ("%5.0d", -1); + X ("%5.0d", 0); + X ("%5.0d", 1); + X ("%-5.0d", -1); + X ("%-5.0d", 0); + X ("%-5.0d", 1); + + P ("%p", NULL); + P ("%p", sprintf_checks); + + S ("%s", (char *) NULL); + S ("%s", "abcd"); + S ("%10s", "abcd"); + S ("%10.5s", "abcdefgh"); + S ("%10.5s", "ab"); + S ("%2.5s", "a"); + S ("%2.5s", "abcdefgh"); + + D ("%4.2d", 1); + D ("%4.2d", 12); + D ("%4.2d", 123); + D ("%4.2d", 1234); + D ("%4.2d", 12345); + D ("%3.3d", 12); + D ("%3.3d", 123); + D ("%3.3d", 1234); + D ("%2.4d", 12345); + D ("%2.4d", 1234); + D ("%2.4d", 123); + D ("%2.4d", 12); + D ("%2.4d", 1); + D ("%.0d", 0); + D ("%.0d", 1); + + S ("%4.2s", "1"); + S ("%4.2s", "12"); + S ("%4.2s", "123"); + S ("%4.2s", "1234"); + S ("%4.2s", "12345"); + S ("%3.3s", "12"); + S ("%3.3s", "123"); + S ("%3.3s", "1234"); + S ("%2.4s", "12345"); + S ("%2.4s", "1234"); + S ("%2.4s", "123"); + S ("%2.4s", "12"); + S ("%2.4s", "1"); + + /* add few more here, if necessary */ +} + +GRUB_UNIT_TEST ("grub_sprintf", sprintf_checks); === added file 'tests/test.c' --- old/tests/test.c 1970-01-01 00:00:00 +0000 +++ new/tests/test.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,150 @@ +#include +#include + +struct grub_test_failure +{ + /* The next failure. */ + struct grub_test_failure *next; + + /* The test source file name. */ + char *file; + + /* The test function name. */ + char *funp; + + /* The test call line number. */ + grub_uint32_t line; + + /* The test failure message. */ + char *message; +}; +typedef struct grub_test_failure *grub_test_failure_t; + +grub_test_t grub_test_list; +static grub_test_failure_t failure_list; + +static void +add_failure (const char *file, + const char *funp, + grub_uint32_t line, const char *fmt, va_list args) +{ + char buf[1024]; + grub_test_failure_t failure; + + failure = (grub_test_failure_t) grub_test_malloc (sizeof (*failure)); + if (!failure) + return; + + grub_test_vsprintf (buf, fmt, args); + + failure->file = grub_test_strdup (file ? : ""); + failure->funp = grub_test_strdup (funp ? : ""); + failure->line = line; + failure->message = grub_test_strdup (buf); + + grub_list_push (GRUB_AS_LIST_P (&failure_list), GRUB_AS_LIST (failure)); +} + +void +free_failures (void) +{ + grub_test_failure_t item; + + while ((item = grub_list_pop (GRUB_AS_LIST_P (&failure_list))) != 0) + { + if (item->message) + grub_test_free (item->message); + + if (item->funp) + grub_test_free (item->funp); + + if (item->file) + grub_test_free (item->file); + + grub_test_free (item); + } + failure_list = 0; +} + +void +grub_test_nonzero (int cond, + const char *file, + const char *funp, grub_uint32_t line, const char *fmt, ...) +{ + va_list ap; + + if (cond) + return; + + va_start (ap, fmt); + add_failure (file, funp, line, fmt, ap); + va_end (ap); +} + +void +grub_test_register (const char *name, void (*test_main) (void)) +{ + grub_test_t test; + + test = (grub_test_t) grub_test_malloc (sizeof (*test)); + if (!test) + return; + + test->name = grub_test_strdup (name); + test->main = test_main; + + grub_list_push (GRUB_AS_LIST_P (&grub_test_list), GRUB_AS_LIST (test)); +} + +void +grub_test_unregister (const char *name) +{ + grub_test_t test; + + test = (grub_test_t) grub_named_list_find + (GRUB_AS_NAMED_LIST (grub_test_list), name); + + if (test) + { + grub_list_remove (GRUB_AS_LIST_P (&grub_test_list), + GRUB_AS_LIST (test)); + + if (test->name) + grub_test_free (test->name); + + grub_test_free (test); + } +} + +int +grub_test_run (const char *name) +{ + grub_test_t test; + + auto int print_failure (grub_test_failure_t item); + int print_failure (grub_test_failure_t item) + { + grub_test_failure_t failure = (grub_test_failure_t) item; + + grub_test_printf (" %s:%s:%u: %s\n", + (failure->file ? : ""), + (failure->funp ? : ""), + failure->line, (failure->message ? : "")); + return 0; + } + + test = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_test_list), name); + if (!test) + return GRUB_ERR_FILE_NOT_FOUND; + + test->main (); + + if (!failure_list) + return GRUB_ERR_NONE; + + grub_test_printf ("%s:\n", test->name); + grub_list_iterate (GRUB_AS_LIST (failure_list), + (grub_list_hook_t) print_failure); + free_failures (); + return GRUB_ERR_TEST_FAILURE; +} === added file 'tests/unit_test.c' --- old/tests/unit_test.c 1970-01-01 00:00:00 +0000 +++ new/tests/unit_test.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +#include +#include +#include + +void * +grub_test_malloc (grub_size_t size) +{ + return malloc (size); +} + +void +grub_test_free (void *ptr) +{ + free (ptr); +} + +int +grub_test_vsprintf (char *str, const char *fmt, va_list args) +{ + return vsprintf (str, fmt, args); +} + +char * +grub_test_strdup (const char *str) +{ + return strdup (str); +} + +int +grub_test_printf (const char *fmt, ...) +{ + int r; + va_list ap; + + va_start (ap, fmt); + r = vprintf (fmt, ap); + va_end (ap); + + return r; +} + +int +main (int argc __attribute__ ((unused)), + char *argv[] __attribute__ ((unused))) +{ + int status = 0; + + extern void grub_unit_test_init (void); + extern void grub_unit_test_fini (void); + + auto int run_test (grub_test_t test); + int run_test (grub_test_t test) + { + status = grub_test_run (test->name) ? : status; + return 0; + } + + grub_unit_test_init (); + grub_list_iterate (GRUB_AS_LIST (grub_test_list), + (grub_list_hook_t) run_test); + grub_unit_test_fini (); + + exit (status); +} + +/* Other misc. functions necessary for successful linking. */ + +char * +grub_env_get (const char *name __attribute__ ((unused))) +{ + return NULL; +} + +grub_err_t +grub_error (grub_err_t n, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + + return n; +} + +void * +grub_malloc (grub_size_t size) +{ + return malloc (size); +} + +void +grub_refresh (void) +{ + fflush (stdout); +} + +void +grub_putchar (int c) +{ + putchar (c); +} + +int +grub_getkey (void) +{ + return -1; +} + +void +grub_exit (void) +{ + exit (1); +} + +struct grub_handler_class grub_term_input_class; +struct grub_handler_class grub_term_output_class; === added directory 'tests/util' === added file 'tests/util/grub-shell-tester.in' --- old/tests/util/grub-shell-tester.in 1970-01-01 00:00:00 +0000 +++ new/tests/util/grub-shell-tester.in 2009-12-20 08:28:15 +0000 @@ -0,0 +1,128 @@ +#! /bin/bash -e + +# Run GRUB script in a Qemu instance +# Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc. +# +# GRUB is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GRUB. If not, see . + +# Initialize some variables. +transform="@program_transform_name@" + address@hidden@ address@hidden@ address@hidden@ address@hidden@ address@hidden@ address@hidden@ address@hidden@ address@hidden@ address@hidden@ + +# Force build directory components +PATH=${builddir}:$PATH +export PATH + +# Usage: usage +# Print the usage. +usage () { + cat <. +EOF +} + +# Check the arguments. +for option in "$@"; do + case "$option" in + -h | --help) + usage + exit 0 ;; + -v | --version) + echo "$0 (GNU GRUB ${PACKAGE_VERSION})" + exit 0 ;; + --modules=*) + ms=`echo "$option" | sed -e 's/--modules=//' -e 's/,/ /g'` + modules="$modules $ms" ;; + -*) + echo "Unrecognized option \`$option'" 1>&2 + usage + exit 1 + ;; + *) + if [ "x${source}" != x ] ; then + echo "too many parameters at the end" 1>&2 + usage + exit 1 + fi + source="${option}" ;; + esac +done + +if [ "x${source}" = x ] ; then + echo "source file must be given" >&2 + usage + exit 1 +fi + +cfgfile=`mktemp` +cat <${cfgfile} +insmod serial +serial +terminal_input serial +terminal_output serial +EOF + +for mod in ${modules} +do + echo "insmod ${mod}" >> ${cfgfile} +done + +cat <>${cfgfile} +source /boot/grub/testcase.cfg +halt +EOF + +isofile=`mktemp` +grub-mkrescue --output=${isofile} --override-directory=${builddir} \ + /boot/grub/grub.cfg=${cfgfile} /boot/grub/testcase.cfg=${source} \ + >/dev/null 2>&1 + +outfile1=`mktemp` +qemu -nographic -serial stdio -cdrom ${isofile} | tr -d "\r" >${outfile1} + +outfile2=`mktemp` +bash ${source} >${outfile2} + +if ! diff -q ${outfile1} ${outfile2} >/dev/null +then + echo "GRUB: ${outfile1}" + hexdump -C ${outfile1} + echo "BASH: ${outfile2}" + hexdump -C ${outfile2} + status=1 +else + rm -f ${cfgfile} ${isofile} ${outfile1} ${outfile2} +fi + +exit $status + + === added file 'util/grub-script-check.c' --- old/util/grub-script-check.c 1970-01-01 00:00:00 +0000 +++ new/util/grub-script-check.c 2009-12-20 08:28:15 +0000 @@ -0,0 +1,248 @@ +/* grub-script-check.c - check grub script file for syntax errors */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include + +#include "progname.h" + +void +grub_putchar (int c) +{ + putchar (c); +} + +int +grub_getkey (void) +{ + return -1; +} + +struct grub_handler_class grub_term_input_class; +struct grub_handler_class grub_term_output_class; + +char * +grub_script_execute_argument_to_string (struct grub_script_arg *arg __attribute__ ((unused))) +{ + return 0; +} + +grub_err_t +grub_script_execute_cmdline (struct grub_script_cmd *cmd __attribute__ ((unused))) +{ + return 0; +} + +grub_err_t +grub_script_execute_cmdblock (struct grub_script_cmd *cmd __attribute__ ((unused))) +{ + return 0; +} + +grub_err_t +grub_script_execute_cmdif (struct grub_script_cmd *cmd __attribute__ ((unused))) +{ + return 0; +} + +grub_err_t +grub_script_execute_menuentry (struct grub_script_cmd *cmd) +{ + struct grub_script_cmd_menuentry *menu; + menu = (struct grub_script_cmd_menuentry *)cmd; + + if (menu->sourcecode) + { + grub_free (menu->sourcecode); + menu->sourcecode = 0; + } + return 0; +} + +grub_err_t +grub_script_execute (struct grub_script *script) +{ + if (script == 0 || script->cmd == 0) + return 0; + + return script->cmd->exec (script->cmd); +} + +static struct option options[] = + { + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + +static void +usage (int status) +{ + if (status) + fprintf (stderr, + "Try ``%s --help'' for more information.\n", program_name); + else + printf ("\ +Usage: %s [PATH]\n\ +\n\ +Checks GRUB script configuration file for syntax errors.\n\ +\n\ + -h, --help display this message and exit\n\ + -V, --version print version information and exit\n\ + -v, --verbose print script being processed\n\ +\n\ +Report bugs to <%s>.\n\ +", program_name, + PACKAGE_BUGREPORT); + exit (status); +} + +int +main (int argc, char *argv[]) +{ + char *argument; + char *input; + FILE *file = 0; + int verbose = 0; + struct grub_script *script; + + auto grub_err_t get_config_line (char **line, int cont); + grub_err_t get_config_line (char **line, int cont __attribute__ ((unused))) + { + char *cmdline = 0; + size_t len = 0; + ssize_t read; + + read = getline(&cmdline, &len, (file ?: stdin)); + if (read == -1) + { + *line = 0; + grub_errno = GRUB_ERR_READ_ERROR; + + if (cmdline) + free (cmdline); + return grub_errno; + } + + if (verbose) + grub_printf("%s", cmdline); + + *line = grub_strdup (cmdline); + + free (cmdline); + return 0; + } + + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + /* Check for options. */ + while (1) + { + int c = getopt_long (argc, argv, "hvV", options, 0); + + if (c == -1) + break; + else + switch (c) + { + case 'h': + usage (0); + break; + + case 'V': + printf ("%s (%s) %s\n", program_name, PACKAGE_NAME, PACKAGE_VERSION); + return 0; + + case 'v': + verbose = 1; + break; + + default: + usage (1); + break; + } + } + + /* Obtain ARGUMENT. */ + if (optind >= argc) + { + file = 0; // read from stdin + } + else if (optind + 1 != argc) + { + fprintf (stderr, "Unknown extra argument `%s'.\n", argv[optind + 1]); + usage (1); + } + else + { + argument = argv[optind]; + file = fopen (argument, "r"); + if (! file) + { + fprintf (stderr, "%s: %s: %s\n", program_name, argument, strerror(errno)); + usage (1); + } + } + + /* Initialize all modules. */ + grub_init_all (); + + do + { + input = 0; + get_config_line(&input, 0); + if (! input) + break; + + script = grub_script_parse (input, get_config_line); + if (script) + { + grub_script_execute (script); + grub_script_free (script); + } + + grub_free (input); + } while (script != 0); + + /* Free resources. */ + grub_fini_all (); + if (file) fclose (file); + + return (script == 0); +}