/* * Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 * NOVELL (All rights reserved) * Copyright (c) 2010 - 2012 * Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. * * This program 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 this program; if not, contact Canonical, Ltd. */ /* Definitions section */ /* %option main */ /* eliminates need to link with libfl */ %option noyywrap %option nounput %option stack %{ #include #include #include #include #include #include #include #include #define _(s) gettext(s) #include "parser.h" #include "profile.h" #include "parser_include.h" #include "parser_yacc.h" #include "lib.h" #ifdef PDEBUG #undef PDEBUG #endif /* #define DEBUG */ #ifdef DEBUG static int yy_top_state(void); #define PDEBUG(fmt, args...) printf("Lexer (Line %d) (state %s): " fmt, current_lineno, state_names[YY_START], ## args) #else #define PDEBUG(fmt, args...) /* Do nothing */ #endif #define NPDEBUG(fmt, args...) /* Do nothing */ #define DUMP_PREPROCESS do { if (preprocess_only) ECHO; } while (0) #define DUMP_AND_DEBUG(X...) \ do { \ DUMP_PREPROCESS; \ PDEBUG(X); \ } while (0) #define EAT_TOKEN(X...) DUMP_AND_DEBUG(X) #define RETURN_TOKEN(X) \ do { \ DUMP_AND_DEBUG("Matched: %s\n", yytext); \ return (X); \ } while (0) #define POP() \ do { \ DUMP_AND_DEBUG(" (pop_to(%s)): Matched: %s\n", state_names[yy_top_state()], yytext); \ yy_pop_state(); \ } while (0) #define PUSH(X) \ do { \ DUMP_AND_DEBUG(" (push(%s)): Matched: %s\n", state_names[(X)], yytext); \ yy_push_state(X); \ } while (0) #define POP_AND_RETURN(X) \ do { \ POP(); \ return (X); \ } while (0) #define PUSH_AND_RETURN(X, Y) \ do { \ PUSH(X); \ return (Y); \ } while (0) #define BEGIN_AND_RETURN(X, Y) \ do { \ DUMP_AND_DEBUG(" (begin(%s)): Matched: %s\n", state_names[(X)], yytext); \ BEGIN(X); \ return (Y); \ } while (0) #define YY_NO_INPUT #define STATE_TABLE_ENT(X) [(X)] = #X /* static char *const state_names[]; */ struct ignored_suffix_t { const char * text; int len; int silent; }; struct ignored_suffix_t ignored_suffixes[] = { /* Debian packging files, which are in flux during install should be silently ignored. */ { ".dpkg-new", 9, 1 }, { ".dpkg-old", 9, 1 }, { ".dpkg-dist", 10, 1 }, { ".dpkg-bak", 9, 1 }, /* RPM packaging files have traditionally not been silently ignored */ { ".rpmnew", 7, 0 }, { ".rpmsave", 8, 0 }, /* Backup files should be mentioned */ { "~", 1, 0 }, { NULL, 0, 0 } }; static int is_blacklisted(const char *name, const char *path) { int name_len; struct ignored_suffix_t *suffix; name_len = strlen(name); /* skip blacklisted suffixes */ for (suffix = ignored_suffixes; suffix->text; suffix++) { char *found; if ( (found = strstr((char *) name, suffix->text)) && found - name + suffix->len == name_len ) { if (!suffix->silent) PERROR("Ignoring: '%s'\n", path); return 1; } } return 0; } struct cb_struct { const char *fullpath; const char *filename; }; static int include_dir_cb(__unused DIR *dir, const char *name, struct stat *st, void *data) { struct cb_struct *d = (struct cb_struct *) data; char *path; /* skip dotfiles silently. */ if (*name == '.') return 0; if (asprintf(&path, "%s/%s", d->fullpath, name) < 0) yyerror("Out of memory"); if (is_blacklisted(name, path)) return 0; if (S_ISREG(st->st_mode)) { if (!(yyin = fopen(path,"r"))) yyerror(_("Could not open '%s' in '%s'"), path, d->filename); PDEBUG("Opened include \"%s\" in \"%s\"\n", path, d->filename); update_mru_tstamp(yyin); push_include_stack(path); yypush_buffer_state(yy_create_buffer(yyin, YY_BUF_SIZE)); } free(path); return 0; } void include_filename(char *filename, int search) { FILE *include_file = NULL; struct stat my_stat; char *fullpath = NULL; if (search) { if (preprocess_only) fprintf(yyout, "\n\n##included <%s>\n", filename); include_file = search_path(filename, &fullpath); } else { if (preprocess_only) fprintf(yyout, "\n\n##included \"%s\"\n", filename); fullpath = strdup(filename); include_file = fopen(fullpath, "r"); } if (!include_file) yyerror(_("Could not open '%s'"), fullpath ? fullpath: filename); if (fstat(fileno(include_file), &my_stat)) yyerror(_("fstat failed for '%s'"), fullpath); if (S_ISREG(my_stat.st_mode)) { yyin = include_file; update_mru_tstamp(include_file); PDEBUG("Opened include \"%s\"\n", fullpath); push_include_stack(fullpath); yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE )); } else if (S_ISDIR(my_stat.st_mode)) { struct cb_struct data = { fullpath, filename }; fclose(include_file); include_file = NULL; if (dirat_for_each(NULL, fullpath, &data, include_dir_cb)) { yyerror(_("Could not process include directory" " '%s' in '%s'"), fullpath, filename);; } } if (fullpath) free(fullpath); } %} CARET "^" OPEN_BRACE \{ CLOSE_BRACE \} SLASH \/ COLON : END_OF_RULE [,] RANGE - MODE_CHARS ([RrWwaLlMmkXx])|(([Pp]|[Cc])[Xx])|(([Pp]|[Cc])?([IiUu])[Xx]) MODES {MODE_CHARS}+ WS [[:blank:]] NUMBER [[:digit:]]+ ID_CHARS [^ \t\n"!,] ID {ID_CHARS}|(,{ID_CHARS}) IDS {ID}+ POST_VAR_ID_CHARS [^ \t\n"!,]{-}[=\+] POST_VAR_ID {POST_VAR_ID_CHARS}|(,{POST_VAR_ID_CHARS}) LIST_VALUE_ID_CHARS [^ \t\n"!,]{-}[()] LIST_VALUE_ID {LIST_VALUE_ID_CHARS}+ QUOTED_LIST_VALUE_ID {LIST_VALUE_ID}|\"{LIST_VALUE_ID}\" ID_CHARS_NOEQ [^ \t\n"!,]{-}[=] LEADING_ID_CHARS_NOEQ [^ \t\n"!,]{-}[=()+&] ID_NOEQ {ID_CHARS_NOEQ}|(,{ID_CHARS_NOEQ}) IDS_NOEQ {LEADING_ID_CHARS_NOEQ}{ID_NOEQ}* ALLOWED_QUOTED_ID [^\0"]|\\\" QUOTED_ID \"{ALLOWED_QUOTED_ID}*\" IP {NUMBER}\.{NUMBER}\.{NUMBER}\.{NUMBER} HAT hat{WS}* PROFILE profile{WS}* KEYWORD [[:alpha:]_]+ VARIABLE_NAME [[:alpha:]][[:alnum:]_]* SET_VAR_PREFIX @ SET_VARIABLE {SET_VAR_PREFIX}(\{{VARIABLE_NAME}\}|{VARIABLE_NAME}) BOOL_VARIABLE $(\{{VARIABLE_NAME}\}|{VARIABLE_NAME}) PATHNAME (\/|{SET_VARIABLE}{POST_VAR_ID}){ID}* QPATHNAME \"(\/|{SET_VAR_PREFIX})([^\0"]|\\\")*\" OPEN_PAREN \( CLOSE_PAREN \) COMMA \, EQUALS = ADD_ASSIGN \+= ARROW -> LT_EQUAL <= /* IF adding new state please update state_names table at eof */ %x SUB_ID %x SUB_VALUE %x EXTCOND_MODE %x EXTCONDLIST_MODE %x NETWORK_MODE %x LIST_VAL_MODE %x LIST_COND_MODE %x LIST_COND_VAL %x LIST_COND_PAREN_VAL %x ASSIGN_MODE %x RLIMIT_MODE %x MOUNT_MODE %x DBUS_MODE %x CHANGE_PROFILE_MODE %x INCLUDE %% %{ /* Copied directly into yylex function */ if (parser_token) { int t = parser_token; parser_token = 0; return t; } %} { {WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ } } { (\<([^\> \t\n]+)\>|\"([^\" \t\n]+)\") { /* */ char *filename = strdup(yytext); filename[strlen(filename) - 1] = '\0'; include_filename(filename + 1, *filename == '<'); free(filename); yy_pop_state(); } [^\<\>\" \t\n]+ { /* filename */ include_filename(yytext, 0); yy_pop_state(); } } <> { fclose(yyin); pop_include_stack(); yypop_buffer_state(); if ( !YY_CURRENT_BUFFER ) yyterminate(); } { {VARIABLE_NAME}/{WS}*= { /* we match to the = in the lexer so that we can switch scanner * state. By the time the parser see the = it may be too late * as bison may have requested the next token from the scanner */ int token = get_keyword_token(yytext); if (token == TOK_PEER) { PUSH_AND_RETURN(EXTCONDLIST_MODE, TOK_CONDLISTID); } else { yylval.id = processid(yytext, yyleng); PUSH_AND_RETURN(EXTCOND_MODE, TOK_CONDID); } } {VARIABLE_NAME}/{WS}+in{WS}*\( { /* we match to 'in' in the lexer so that we can switch scanner * state. By the time the parser see the 'in' it may be to * late as bison may have requested the next token from the * scanner */ yylval.id = processid(yytext, yyleng); PUSH_AND_RETURN(EXTCOND_MODE, TOK_CONDID); } } { ({IDS}|{QUOTED_ID}) { /* Go into separate state to match generic ID strings */ yylval.id = processid(yytext, yyleng); POP_AND_RETURN(TOK_ID); } } { ({IDS}|{QUOTED_ID}) { /* Go into separate state to match generic VALUE strings */ yylval.id = processid(yytext, yyleng); POP_AND_RETURN(TOK_VALUE); } } { {CLOSE_PAREN} { POP_AND_RETURN(TOK_CLOSEPAREN); } {COMMA} { EAT_TOKEN("listval: ,\n"); } ({LIST_VALUE_ID}|{QUOTED_ID}) { yylval.id = processid(yytext, yyleng); RETURN_TOKEN(TOK_VALUE); } } { {EQUALS}{WS}*/[^(\n]{-}{WS} { BEGIN_AND_RETURN(SUB_VALUE, TOK_EQUALS);} {EQUALS} { RETURN_TOKEN(TOK_EQUALS); } /* Don't push state here as this is a transition start condition and * we want to return to the start condition that invoked * when LIST_VAL_ID is done */ {OPEN_PAREN} { BEGIN_AND_RETURN(LIST_VAL_MODE, TOK_OPENPAREN); } in { RETURN_TOKEN(TOK_IN); } } { ({LIST_VALUE_ID}|{QUOTED_LIST_VALUE_ID}) { yylval.id = processid(yytext, yyleng); POP_AND_RETURN(TOK_VALUE); } } { {CLOSE_PAREN} { POP(); } ({LIST_VALUE_ID}|{QUOTED_LIST_VALUE_ID}) { yylval.id = processid(yytext, yyleng); RETURN_TOKEN(TOK_VALUE); } } { {CLOSE_PAREN} { POP_AND_RETURN(TOK_CLOSEPAREN); } {COMMA} { EAT_TOKEN("listcond: , \n"); } {ID_CHARS_NOEQ}+ { yylval.id = processid(yytext, yyleng); RETURN_TOKEN(TOK_CONDID); } {EQUALS}{WS}*{OPEN_PAREN} { PUSH_AND_RETURN(LIST_COND_PAREN_VAL, TOK_EQUALS); } {EQUALS} { PUSH_AND_RETURN(LIST_COND_VAL, TOK_EQUALS); } } { {EQUALS} { RETURN_TOKEN(TOK_EQUALS); } {OPEN_PAREN} { /* Don't push state here as this is a transition * start condition and we want to return to the start * condition that invoked when * LIST_VAL_ID is done */ BEGIN_AND_RETURN(LIST_COND_MODE, TOK_OPENPAREN); } } { ({IDS}|{QUOTED_ID}) { yylval.var_val = processid(yytext, yyleng); RETURN_TOKEN(TOK_VALUE); } {END_OF_RULE} { yylval.id = strdup(yytext); DUMP_PREPROCESS; yyerror(_("Variable declarations do not accept trailing commas")); } \\\n { DUMP_PREPROCESS; current_lineno++ ; } \r?\n { DUMP_PREPROCESS; current_lineno++; yy_pop_state(); } } { {IDS} { yylval.id = strdup(yytext); RETURN_TOKEN(TOK_ID); } } { {ARROW} { RETURN_TOKEN(TOK_ARROW); } ({IDS}|{QUOTED_ID}) { yylval.id = processid(yytext, yyleng); POP_AND_RETURN(TOK_ID); } } { -?{NUMBER}[[:alpha:]]* { yylval.var_val = strdup(yytext); RETURN_TOKEN(TOK_VALUE); } {KEYWORD} { yylval.id = strdup(yytext); if (strcmp(yytext, "infinity") == 0) RETURN_TOKEN(TOK_VALUE); RETURN_TOKEN(TOK_ID); } {LT_EQUAL} { RETURN_TOKEN(TOK_LE); } } { send { RETURN_TOKEN(TOK_SEND); } receive { RETURN_TOKEN(TOK_RECEIVE); } bind { RETURN_TOKEN(TOK_BIND); } read { RETURN_TOKEN(TOK_READ); } write { RETURN_TOKEN(TOK_WRITE); } {OPEN_PAREN} { yy_push_state(LIST_VAL_MODE); RETURN_TOKEN(TOK_OPENPAREN); } (r|w|rw|wr)/([[:space:],]) { yylval.mode = strdup(yytext); RETURN_TOKEN(TOK_MODE); } } { {ARROW} { RETURN_TOKEN(TOK_ARROW); } ({IDS_NOEQ}|{PATHNAME}|{QUOTED_ID}) { yylval.id = processid(yytext, yyleng); RETURN_TOKEN(TOK_ID); } } #include/.*\r?\n { PUSH(INCLUDE); } #.*\r?\n { /* normal comment */ DUMP_AND_DEBUG("comment(%d): %s\n", current_lineno, yytext); current_lineno++; } {CARET} { PUSH_AND_RETURN(SUB_ID, TOK_CARET); } {ARROW} { RETURN_TOKEN(TOK_ARROW); } {EQUALS} { PUSH_AND_RETURN(ASSIGN_MODE, TOK_EQUALS); } {ADD_ASSIGN} { PUSH_AND_RETURN(ASSIGN_MODE, TOK_ADD_ASSIGN); } {SET_VARIABLE} { yylval.set_var = strdup(yytext); RETURN_TOKEN(TOK_SET_VAR); } {BOOL_VARIABLE} { yylval.bool_var = strdup(yytext); RETURN_TOKEN(TOK_BOOL_VAR); } {OPEN_BRACE} { RETURN_TOKEN(TOK_OPEN); } {CLOSE_BRACE} { RETURN_TOKEN(TOK_CLOSE); } ({PATHNAME}|{QPATHNAME}) { yylval.id = processid(yytext, yyleng); RETURN_TOKEN(TOK_ID); } ({MODES})/([[:space:],]) { yylval.mode = strdup(yytext); RETURN_TOKEN(TOK_MODE); } {HAT} { PUSH_AND_RETURN(SUB_ID, TOK_HAT); } {PROFILE} { PUSH_AND_RETURN(SUB_ID, TOK_PROFILE); } {COLON} { RETURN_TOKEN(TOK_COLON); } {OPEN_PAREN} { PUSH_AND_RETURN(LIST_VAL_MODE, TOK_OPENPAREN); } {VARIABLE_NAME} { DUMP_PREPROCESS; int token = get_keyword_token(yytext); int state = INITIAL; /* special cases */ switch (token) { case -1: /* no token found */ yylval.id = processunquoted(yytext, yyleng); RETURN_TOKEN(TOK_ID); break; case TOK_RLIMIT: state = RLIMIT_MODE; break; case TOK_NETWORK: state = NETWORK_MODE; break; case TOK_CHANGE_PROFILE: state = CHANGE_PROFILE_MODE; break; case TOK_MOUNT: case TOK_REMOUNT: case TOK_UMOUNT: state = MOUNT_MODE; break; case TOK_DBUS: state = DBUS_MODE; break; default: /* nothing */ break; } PUSH_AND_RETURN(state, token); } { {END_OF_RULE} { if (YY_START != INITIAL) yy_pop_state(); RETURN_TOKEN(TOK_END_OF_RULE); } \r?\n { DUMP_PREPROCESS; current_lineno++; } } { [^\n] { DUMP_PREPROCESS; /* Something we didn't expect */ yyerror(_("Found unexpected character: '%s'"), yytext); } } %% /* Create a table mapping lexer state number to the name used in the * in the code. This allows for better debug output */ static const char *const state_names[] = { STATE_TABLE_ENT(INITIAL), STATE_TABLE_ENT(SUB_ID), STATE_TABLE_ENT(SUB_VALUE), STATE_TABLE_ENT(EXTCOND_MODE), STATE_TABLE_ENT(EXTCONDLIST_MODE), STATE_TABLE_ENT(NETWORK_MODE), STATE_TABLE_ENT(LIST_VAL_MODE), STATE_TABLE_ENT(LIST_COND_MODE), STATE_TABLE_ENT(LIST_COND_VAL), STATE_TABLE_ENT(LIST_COND_PAREN_VAL), STATE_TABLE_ENT(ASSIGN_MODE), STATE_TABLE_ENT(RLIMIT_MODE), STATE_TABLE_ENT(MOUNT_MODE), STATE_TABLE_ENT(DBUS_MODE), STATE_TABLE_ENT(CHANGE_PROFILE_MODE), STATE_TABLE_ENT(INCLUDE), };