#include #include #ifdef HAVE_LUA_H # include #endif #include #define KV_LISTING_MAX 100 typedef enum { TK_ERROR, TK_EOF, TK_AND, TK_ASSIGN, TK_ASSOCICATE, TK_CAST_STRING, TK_CAST_INT, TK_COMMA, TK_CURLY_CLOSE, TK_CURLY_OPEN, TK_DEFAULT, TK_DIVIDE, TK_ELSE, TK_EQUAL, TK_FALSE, TK_GLOBAL, TK_GREATER, TK_GREATER_EQUAL, TK_IF, TK_INCLUDE, TK_INCLUDE_LUA, TK_INCLUDE_SHELL, TK_INTEGER, TK_LESS, TK_LESS_EQUAL, TK_LOCAL, TK_MATCH, TK_MINUS, TK_MULTIPLY, TK_NAME, TK_NONE, TK_NOT, TK_NOT_EQUAL, TK_NOT_MATCH, TK_NOT_PREFIX, TK_NOT_SUBNET, TK_NOT_SUFFIX, TK_OR, TK_PARA_CLOSE, TK_PARA_OPEN, TK_PLUS, TK_PREFIX, TK_SEMICOLON, TK_SETUP, TK_SQUARE_CLOSE, TK_SQUARE_OPEN, TK_STRING, TK_SUBNET, TK_SUFFIX, TK_TRUE } liConfigToken; typedef struct liConfigScope liConfigScope; struct liConfigScope { liConfigScope *parent; GHashTable *variables; }; typedef struct liConfigTokenizerContext liConfigTokenizerContext; struct liConfigTokenizerContext { liServer *srv; liWorker *wrk; gboolean master_config; /* whether includes/changing global vars are allowed */ /* ragel vars */ int cs; const char *p, *pe, *eof; /* remember token start position for error messages */ const char *token_start; gsize token_line; const gchar *token_line_start; /* mark start of strings and similar */ const gchar *mark; /* number stuff */ gboolean negative; gint64 suffix_factor; /* information about currently parsed file */ const gchar *filename; const gchar *content; /* pointer to the data */ gsize len; gsize line; /* holds current line */ const gchar *line_start; /* result */ GString *token_string; gint64 token_number; /* TK_ERROR => have to parse it. parsing returns the token, but you can put it back here */ /* only put the last token back, as on the next parse call token_string and token_number get reset */ liConfigToken next_token; /* variable storage */ liConfigScope *current_scope; }; typedef struct liConditionTree liConditionTree; struct liConditionTree { gboolean negated; liCondition *condition; liConfigToken op; liConditionTree *left, *right; }; static liConfigToken tokenizer_error(liConfigTokenizerContext *ctx, GError **error, const char *fmt, ...) G_GNUC_PRINTF(3, 4); GQuark li_config_error_quark(void) { return g_quark_from_string("li-config-error-quark"); } %%{ ## ragel stuff machine config_tokenizer; variable p ctx->p; variable pe ctx->pe; variable eof ctx->eof; access ctx->; action mark { ctx->mark = fpc; } action name { g_string_append_len(ctx->token_string, ctx->mark, fpc - ctx->mark); return TK_NAME; } action char { g_string_append_c(ctx->token_string, fc); } action echar { switch (fc) { case 't': g_string_append_c(ctx->token_string, '\t'); break; case 'r': g_string_append_c(ctx->token_string, '\r'); break; case 'n': g_string_append_c(ctx->token_string, '\n'); break; case '"': case '\'': case '\\': g_string_append_c(ctx->token_string, fc); break; default: g_string_append_c(ctx->token_string, '\\'); g_string_append_c(ctx->token_string, fc); break; } } action xchar { char xstr[3] = " "; xstr[0] = fpc[-1]; xstr[1] = fpc[0]; g_string_append_c(ctx->token_string, strtol(xstr, NULL, 16)); } action string { ++fpc; return TK_STRING; } action e_char { g_string_append_c(ctx->token_string, fc); } action e_echar { switch (fc) { case 't': g_string_append_c(ctx->token_string, '\t'); break; case 'r': g_string_append_c(ctx->token_string, '\r'); break; case 'n': g_string_append_c(ctx->token_string, '\n'); break; default: g_string_append_c(ctx->token_string, fc); break; } } action e_xchar { char xstr[3] = " "; xstr[0] = fpc[-1]; xstr[1] = fpc[0]; g_string_append_c(ctx->token_string, strtol(xstr, NULL, 16)); } action e_string { ++fpc; return TK_STRING; } action int_start { ctx->negative = FALSE; ctx->token_number = 0; ctx->suffix_factor = 1; } action int_negative { ctx->negative = TRUE; } action dec_digit { gint64 digit = fc - '0'; if (ctx->negative) { if (ctx->token_number < G_MININT64 / 10 + digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 10*ctx->token_number - digit; } else { if (ctx->token_number > G_MAXINT64 / 10 - digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 10*ctx->token_number + digit; } } action oct_digit { gint64 digit = fc - '0'; if (ctx->negative) { if (ctx->token_number < G_MININT64 / 8 + digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 8*ctx->token_number - digit; } else { if (ctx->token_number > G_MAXINT64 / 8 - digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 8*ctx->token_number + digit; } } action hex_digit { gint64 digit; if (fc >= '0' && fc <= '9') digit = fc - '0'; else if (fc >= 'a' && fc <= 'f') digit = fc - 'a' + 10; else /*if (fc >= 'A' && fc <= 'F')*/ digit = fc - 'A' + 10; if (ctx->negative) { if (ctx->token_number < G_MININT64 / 16 + digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 16*ctx->token_number - digit; } else { if (ctx->token_number > G_MAXINT64 / 16 - digit) { return tokenizer_error(ctx, error, "integer overflow"); } ctx->token_number = 16*ctx->token_number + digit; } } action integer_suffix { if (ctx->negative) { if (ctx->token_number < G_MININT64 / ctx->suffix_factor) { return tokenizer_error(ctx, error, "integer overflow in suffix"); } } else if (ctx->token_number < 0) { if (ctx->token_number > G_MAXINT64 / ctx->suffix_factor) { return tokenizer_error(ctx, error, "integer overflow in suffix"); } } ctx->token_number *= ctx->suffix_factor; } action integer { return TK_INTEGER; } noise_char = [\t \r\n#]; operator_char = [+\-*/=<>!^$~;,(){}[\]] | '"' | "'"; operator_separator_char = [;,(){}[\]]; keywords = ( 'and' | 'default' | 'cast' | 'else' | 'false' | 'global' | 'if' | 'include' | 'include_lua' | 'include_shell' | 'local' | 'none' | 'not' | 'or' | 'setup' | 'true' ); line_sane = ( '\n' ) >{ ctx->line++; ctx->line_start = fpc + 1; }; line_weird = ( '\r' ) >{ ctx->line++; ctx->line_start = fpc + 1; }; line_insane = ( '\r' ( '\n' >{ ctx->line--; } ) ); line = ( line_sane | line_weird | line_insane ); ws = ( '\t' | ' ' ); comment = ( '#' (any - line)* line ); noise = ( ws | line | comment )**; integer_suffix_bytes = ( 'byte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1); } | 'kbyte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024); } | 'mbyte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024; } | 'gbyte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024; } | 'tbyte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024*1024; } | 'pbyte' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024*1024*1024; } ); integer_suffix_bits = ( 'bit' %{ ctx->token_number /= 8; ctx->suffix_factor = G_GINT64_CONSTANT(1); } | 'kbit' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024) / 8; } | 'mbit' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024 / 8; } | 'gbit' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024 / 8; } | 'tbit' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024*1024 / 8; } | 'pbit' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1024)*1024*1024*1024*1024 / 8; } ); integer_suffix_seconds = ( 'sec' %{ ctx->suffix_factor = G_GINT64_CONSTANT(1); } | 'min' %{ ctx->suffix_factor = G_GINT64_CONSTANT(60); } | 'hours' %{ ctx->suffix_factor = G_GINT64_CONSTANT(3600); } | 'days' %{ ctx->suffix_factor = G_GINT64_CONSTANT(24)*3600; } ); integer_suffix = ( integer_suffix_bytes | integer_suffix_bits | integer_suffix_seconds ) %integer_suffix; integer_base = ('-'@int_negative)? ( ([1-9] [0-9]*)$dec_digit | '0' | '0' ([0-9]+)$oct_digit | '0x' (xdigit+)$hex_digit )>int_start; # noise_char only matches after integer_suffix, as noise matches all noice chars greedy integer = (integer_base noise integer_suffix?)%integer (noise_char | operator_char); string = ( ( '"' ( (any - [\\"])@char | ('\\' [^x])@echar | ('\\x' xdigit{2})@xchar )* '"' ) @string | ( "'" ( (any - [\\'])@char | ('\\' [^x])@echar | ('\\x' xdigit{2})@xchar )* "'" ) @string | ( 'e"' ( (any - [\\"])@e_char | ('\\' [trn\\"'])@e_echar | ('\\x' xdigit{2})@e_xchar )* '"' ) @e_string | ( "e'" ( (any - [\\'])@e_char | ('\\' [trn\\"'])@e_echar | ('\\x' xdigit{2})@e_xchar )* "'" ) @e_string ); keyword = ( 'and' %{ return TK_AND; } | 'default' %{ return TK_DEFAULT; } | 'else' %{ return TK_ELSE; } | 'false' %{ return TK_FALSE; } | 'global' %{ return TK_GLOBAL; } | 'if' %{ return TK_IF; } | 'include' %{ return TK_INCLUDE; } | 'include_lua' %{ return TK_INCLUDE_LUA; } | 'include_shell' %{ return TK_INCLUDE_SHELL; } | 'local' %{ return TK_LOCAL; } | 'none' %{ return TK_NONE; } | 'not' %{ return TK_NOT; } | 'or' %{ return TK_OR; } | 'setup' %{ return TK_SETUP; } | 'true' %{ return TK_TRUE; } ) %(identifier, 1) (noise_char | operator_char)?; name = ( (alpha | '_') (alnum | [._])* - keywords) >mark %name (noise_char | operator_char)?; cast = ( 'cast' noise '(' noise 'int' noise ')' @{ ++fpc; return TK_CAST_INT; } | 'cast' noise '(' noise 'string' noise ')' @{ ++fpc; return TK_CAST_STRING; } ); single_operator = ( ';' @{ ++fpc; return TK_SEMICOLON; } | ',' @{ ++fpc; return TK_COMMA; } | '(' @{ ++fpc; return TK_PARA_OPEN; } | ')' @{ ++fpc; return TK_PARA_CLOSE; } | '[' @{ ++fpc; return TK_SQUARE_OPEN; } | ']' @{ ++fpc; return TK_SQUARE_CLOSE; } | '{' @{ ++fpc; return TK_CURLY_OPEN; } | '}' @{ ++fpc; return TK_CURLY_CLOSE; } ); operator = # minus not accepted here in front of a digit '-' %{ return TK_MINUS; } (noise_char | operator_separator_char | alpha | '_' | '"' | "'")? | ( '+' %{ return TK_PLUS; } | '*' %{ return TK_MULTIPLY; } | '/' %{ return TK_DIVIDE; } | '!' %{ return TK_NOT; } | '==' %{ return TK_EQUAL; } | '!=' %{ return TK_NOT_EQUAL; } | '=^' %{ return TK_PREFIX; } | '!^' %{ return TK_NOT_PREFIX; } | '=$' %{ return TK_SUFFIX; } | '!$' %{ return TK_NOT_SUFFIX; } | '<' %{ return TK_LESS; } | '<=' %{ return TK_LESS_EQUAL; } | '>' %{ return TK_GREATER; } | '>=' %{ return TK_GREATER_EQUAL; } | '=~' %{ return TK_MATCH; } | '!~' %{ return TK_NOT_MATCH; } | '=/' %{ return TK_SUBNET; } | '!/' %{ return TK_NOT_SUBNET; } | '=' %{ return TK_ASSIGN; } | '=>' %{ return TK_ASSOCICATE; } ) (noise_char | operator_separator_char | alnum | '_' | '"' | "'")?; endoffile = '' %{ fpc = NULL; return TK_EOF; }; token := noise ( endoffile | keyword | name | cast | single_operator | operator | integer | string ); }%% %% write data; static void set_config_error(liConfigTokenizerContext *ctx, GError **error, const char *fmt, va_list ap) { GString *msg = g_string_sized_new(127); g_string_vprintf(msg, fmt, ap); g_set_error(error, LI_CONFIG_ERROR, 0, "error in %s:%" G_GSIZE_FORMAT ":%" G_GSIZE_FORMAT ": %s", ctx->filename, ctx->token_line, 1 + ctx->token_start - ctx->token_line_start, msg->str); g_string_free(msg, TRUE); } static liConfigToken tokenizer_error(liConfigTokenizerContext *ctx, GError **error, const char *fmt, ...) { va_list ap; va_start(ap, fmt); set_config_error(ctx, error, fmt, ap); va_end(ap); return TK_ERROR; } static gboolean parse_error(liConfigTokenizerContext *ctx, GError **error, const char *fmt, ...) { va_list ap; va_start(ap, fmt); set_config_error(ctx, error, fmt, ap); va_end(ap); return FALSE; } static void tokenizer_init(liServer *srv, liWorker *wrk, liConfigTokenizerContext *ctx, const gchar *filename, const gchar *content, gsize len) { memset(ctx, 0, sizeof(*ctx)); ctx->srv = srv; ctx->wrk = wrk; ctx->p = content; ctx->pe = ctx->eof = content + len; ctx->filename = filename; ctx->content = content; ctx->len = len; ctx->line = 1; ctx->line_start = ctx->content; ctx->token_string = g_string_sized_new(31); } static gboolean tokenizer_init_file(liServer *srv, liWorker *wrk, liConfigTokenizerContext *ctx, const gchar *filename, GError **error) { memset(ctx, 0, sizeof(*ctx)); ctx->srv = srv; ctx->wrk = wrk; if (!g_file_get_contents(filename, (gchar**) &ctx->content, &ctx->len, error)) return FALSE; ctx->p = ctx->content; ctx->pe = ctx->eof = ctx->content + ctx->len; ctx->filename = filename; ctx->line = 1; ctx->line_start = ctx->content; ctx->token_string = g_string_sized_new(31); return TRUE; } static void tokenizer_clear(liConfigTokenizerContext *ctx) { g_string_free(ctx->token_string, TRUE); } static liConfigToken tokenizer_next(liConfigTokenizerContext *ctx, GError **error) { g_string_truncate(ctx->token_string, 0); ctx->token_number = 0; if (NULL == ctx->p) return tokenizer_error(ctx, error, "already reached end of file"); ctx->token_start = ctx->p; ctx->token_line = ctx->line; ctx->token_line_start = ctx->line_start; %% write init; %% write exec; return tokenizer_error(ctx, error, "couldn't parse token"); } #define NEXT(token) do { \ if (TK_ERROR != ctx->next_token) { token = ctx->next_token; ctx->next_token = TK_ERROR; } \ else if (TK_ERROR == (token = tokenizer_next(ctx, error))) goto error; \ } while (0) #define REMEMBER(token) do { \ LI_FORCE_ASSERT(TK_ERROR == ctx->next_token); /* mustn't contain a token */ \ ctx->next_token = token; \ } while (0) static void scope_enter(liConfigTokenizerContext *ctx) { liConfigScope *scope = g_slice_new0(liConfigScope); scope->parent = ctx->current_scope; scope->variables = li_value_new_hashtable(); ctx->current_scope = scope; } static void scope_leave(liConfigTokenizerContext *ctx) { liConfigScope *scope = ctx->current_scope; LI_FORCE_ASSERT(NULL != scope); ctx->current_scope = scope->parent; g_hash_table_destroy(scope->variables); scope->variables = NULL; scope->parent = NULL; g_slice_free(liConfigScope, scope); } /* copy name, takeover value */ static gboolean scope_setvar(liConfigTokenizerContext *ctx, GString *name, liValue *value, GError **error) { liConfigScope *scope; if (g_str_has_prefix(name->str, "sys.")) { li_value_free(value); return parse_error(ctx, error, "sys.* variables are read only"); } scope = ctx->current_scope; /* search whether name already exists in a scope. otherwise make it local */ while (NULL != scope) { if (NULL != g_hash_table_lookup(scope->variables, name)) { g_hash_table_insert(scope->variables, g_string_new_len(GSTR_LEN(name)), value); return TRUE; } scope = scope->parent; } if (ctx->master_config) { /* modify global vars in master config */ if (NULL != ctx->srv->config_global_vars && NULL != g_hash_table_lookup(ctx->srv->config_global_vars, name)) { g_hash_table_insert(ctx->srv->config_global_vars, g_string_new_len(GSTR_LEN(name)), value); return TRUE; } } if (NULL != ctx->current_scope) { g_hash_table_insert(ctx->current_scope->variables, g_string_new_len(GSTR_LEN(name)), value); return TRUE; } /* no scope... do nothing */ li_value_free(value); return parse_error(ctx, error, "can't write variable without scope"); } /* copy name, takeover value */ static gboolean scope_local_setvar(liConfigTokenizerContext *ctx, GString *name, liValue *value, GError **error) { if (g_str_has_prefix(name->str, "sys.")) { li_value_free(value); return parse_error(ctx, error, "sys.* variables are read only"); } if (NULL != ctx->current_scope) { g_hash_table_insert(ctx->current_scope->variables, g_string_new_len(GSTR_LEN(name)), value); return TRUE; } /* no scope... do nothing */ li_value_free(value); return parse_error(ctx, error, "can't write variable without scope"); } /* copy name, takeover value */ static gboolean scope_global_setvar(liConfigTokenizerContext *ctx, GString *name, liValue *value, GError **error) { if (g_str_has_prefix(name->str, "sys.")) { li_value_free(value); return parse_error(ctx, error, "sys.* variables are read only"); } if (!ctx->master_config) { li_value_free(value); return parse_error(ctx, error, "modifying global variables not allowed in this context"); } if (NULL == ctx->srv->config_global_vars) { li_value_free(value); return parse_error(ctx, error, "no global variable scope"); } g_hash_table_insert(ctx->srv->config_global_vars, g_string_new_len(GSTR_LEN(name)), value); return TRUE; } /* read-only pointer, no reference, return NULL if not found - no error */ /* only to read "action" variables */ static liValue* scope_peekvar(liConfigTokenizerContext *ctx, GString *name) { liConfigScope *scope = ctx->current_scope; liValue *value; /* can't peek sys. vars */ if (g_str_has_prefix(name->str, "sys.")) return NULL; while (NULL != scope) { if (NULL != (value = g_hash_table_lookup(scope->variables, name))) { return value; } scope = scope->parent; } if (NULL != ctx->srv->config_global_vars && NULL != (value = g_hash_table_lookup(ctx->srv->config_global_vars, name))) { return value; } return NULL; } static liValue* scope_getvar(liConfigTokenizerContext *ctx, GString *name, GError **error) { liValue *value; if (g_str_has_prefix(name->str, "sys.")) { if (g_str_equal(name->str, "sys.pid")) { return li_value_new_number(getpid()); } else if (g_str_equal(name->str, "sys.cwd")) { gchar cwd[1024]; if (NULL != getcwd(cwd, sizeof(cwd)-1)) { return li_value_new_string(g_string_new(cwd)); } else { parse_error(ctx, error, "failed to get CWD: %s", g_strerror(errno)); return NULL; } } else if (g_str_equal(name->str, "sys.version")) { return li_value_new_string(g_string_new(PACKAGE_VERSION)); } else if (g_str_has_prefix(name->str, "sys.env.")) { /* look up string in environment, push value onto stack */ gchar *env = getenv(name->str + sizeof("sys.env.") - 1); if (env == NULL) { parse_error(ctx, error, "undefined environment variable: %s", name->str + sizeof("sys.env.") - 1); return NULL; } return li_value_new_string(g_string_new(env)); } parse_error(ctx, error, "unknown sys.* variable: %s", name->str); return NULL; } value = scope_peekvar(ctx, name); if (NULL != value) return li_value_copy(value); parse_error(ctx, error, "undefined variable '%s'", name->str); return NULL; } static gboolean p_include(liAction *list, liConfigTokenizerContext *ctx, GError **error); static gboolean p_include_lua(liAction *list, liConfigTokenizerContext *ctx, GError **error); static gboolean p_include_shell(liAction *list, liConfigTokenizerContext *ctx, GError **error); static gboolean p_setup(GString *name, liConfigTokenizerContext *ctx, GError **error); static gboolean p_setup_block(liConfigTokenizerContext *ctx, GError **error); static gboolean p_action(liAction *list, GString *name, liConfigTokenizerContext *ctx, GError **error); static gboolean p_actions(gboolean block, liAction *list, liConfigTokenizerContext *ctx, GError **error); static gboolean p_value_list(gint *key_value_nesting, liValue **result, gboolean key_value_list, liValue *pre_value, liConfigToken end, liConfigTokenizerContext *ctx, GError **error); static gboolean p_value_group(gint *key_value_nesting, liValue **value, liConfigTokenizerContext *ctx, GError **error); static gboolean p_value(gint *key_value_nesting, liValue **value, liConfigToken preOp, liConfigTokenizerContext *ctx, GError **error); static gboolean p_vardef(GString *name, int normalLocalGlobal, liConfigTokenizerContext *ctx, GError **error); static gboolean p_parameter_values(liValue **result, liConfigTokenizerContext *ctx, GError **error); static liAction* cond_walk(liServer *srv, liConditionTree *tree, liAction *positive, liAction *negative); static gboolean p_condition_value(liConditionTree **cond, liConfigTokenizerContext *ctx, GError **error); static gboolean p_condition_expr(liConditionTree **tree, liConfigToken preOp, liConfigTokenizerContext *ctx, GError **error); static gboolean p_condition(liAction *list, liConfigTokenizerContext *ctx, GError **error); /* whether token can be start of a value */ static gboolean is_value_start_token(liConfigToken token) { switch (token) { case TK_CAST_STRING: case TK_CAST_INT: case TK_CURLY_OPEN: case TK_DEFAULT: case TK_FALSE: case TK_INTEGER: case TK_MINUS: case TK_NAME: case TK_NONE: case TK_NOT: case TK_PARA_OPEN: case TK_SQUARE_OPEN: case TK_STRING: case TK_TRUE: return TRUE; default: return FALSE; } } static int _op_precedence(liConfigToken op) { switch (op) { case TK_OR: return 1; case TK_AND: return 2; case TK_PLUS: case TK_MINUS: return 3; case TK_MULTIPLY: case TK_DIVIDE: return 4; default: return 0; } } /* returns TRUE iff a b c == (a b) c * returns TRUE if isn't an operator * returns FALSE if isn't an operator but is */ static gboolean operator_precedence(liConfigToken op1, liConfigToken op2) { return _op_precedence(op1) >= _op_precedence(op2); } /* overflow checks, assuming two-complement representation, i.e. G_MININT64 == (-G_MAXINT64)+ - 1, -G_MININT64 "==" G_MININT64 */ static gboolean overflow_op_plus_int(gint64 a, gint64 b) { if (a < 0) { if (b >= 0) return FALSE; return (a < G_MININT64 - b); } else { if (b <= 0) return FALSE; return (a > G_MAXINT64 - b); } } static gboolean overflow_op_minus_int(gint64 a, gint64 b) { if (a < 0) { if (b <= 0) return FALSE; return (a < G_MININT64 + b); } else { if (b >= 0) return FALSE; return (a > G_MAXINT64 + b); } } static gboolean overflow_op_multiply_int(gint64 a, gint64 b) { if (0 == a || 0 == b) return FALSE; if (a < 0) { if (b > 0) return (a < G_MININT64 / b); if (G_MININT64 == a || G_MININT64 == b) return TRUE; /* b < 0 */ return ((-a) > G_MAXINT64 / (-b)); } else { /* a > 0 */ if (b < 0) return (b < G_MININT64 / a); /* b > 0 */ return (a > G_MAXINT64 / b); } } static gboolean overflow_op_divide_int(gint64 a, gint64 b) { /* only MIN INT / -1 == MAX INT + 1 overflows */ return (-1 == b && G_MININT64 == a); } /* always frees v1 and v2 */ static gboolean op_execute(liValue **vresult, liConfigToken op, liValue *v1, liValue *v2, liConfigTokenizerContext *ctx, GError **error) { if (li_value_type(v1) == li_value_type(v2)) { switch (li_value_type(v1)) { case LI_VALUE_NUMBER: switch (op) { case TK_AND: case TK_OR: parse_error(ctx, error, "boolean operations not allowed on numbers"); goto error; case TK_PLUS: if (overflow_op_plus_int(v1->data.number, v2->data.number)) { parse_error(ctx, error, "overflow in addition"); goto error; } *vresult = li_value_new_number(v1->data.number + v2->data.number); break; case TK_MINUS: if (overflow_op_minus_int(v1->data.number, v2->data.number)) { parse_error(ctx, error, "overflow in subtraction"); goto error; } *vresult = li_value_new_number(v1->data.number - v2->data.number); break; case TK_MULTIPLY: if (overflow_op_multiply_int(v1->data.number, v2->data.number)) { parse_error(ctx, error, "overflow in product"); goto error; } *vresult = li_value_new_number(v1->data.number + v2->data.number); break; case TK_DIVIDE: if (0 == v2->data.number) { parse_error(ctx, error, "divide by zero"); goto error; } if (overflow_op_divide_int(v1->data.number, v2->data.number)) { parse_error(ctx, error, "overflow in quotient"); goto error; } *vresult = li_value_new_number(v1->data.number + v2->data.number); break; default: parse_error(ctx, error, "unsupported operation on numbers"); goto error; } break; case LI_VALUE_LIST: switch (op) { case TK_PLUS: *vresult = li_value_extract(v1); LI_VALUE_FOREACH(entry, v2) li_value_list_append(*vresult, li_value_extract(entry)); LI_VALUE_END_FOREACH() break; default: parse_error(ctx, error, "unsupported operation on lists"); goto error; } break; case LI_VALUE_STRING: switch (op) { case TK_PLUS: g_string_append_len(v1->data.string, GSTR_LEN(v2->data.string)); *vresult = v1; v1 = NULL; break; default: parse_error(ctx, error, "unsupported operation on strings"); goto error; } break; default: parse_error(ctx, error, "operations (+,-,*,/) on %s not supported", li_value_type_string(v1)); goto error; } } else { parse_error(ctx, error, "operations (+,-,*,/) on mixed value types not allowed"); goto error; } li_value_free(v1); li_value_free(v2); return TRUE; error: li_value_free(v1); li_value_free(v2); return FALSE; } static gboolean include_file(liAction *list, GString *filename, liConfigTokenizerContext *ctx, GError **error) { liConfigTokenizerContext subctx; gboolean result; if (!tokenizer_init_file(ctx->srv, ctx->wrk, &subctx, filename->str, error)) { return FALSE; } subctx.master_config = ctx->master_config; scope_enter(&subctx); subctx.current_scope->parent = ctx->current_scope; result = p_actions(FALSE, list, &subctx, error); scope_leave(&subctx); g_free((gchar*) subctx.content); tokenizer_clear(&subctx); return result; } static gboolean p_include(liAction *list, liConfigTokenizerContext *ctx, GError **error) { liValue *parameters = NULL; liValue *val_fname; GString *fname; if (!p_parameter_values(¶meters, ctx, error)) return FALSE; val_fname = li_value_get_single_argument(parameters); if (NULL == val_fname || LI_VALUE_STRING != li_value_type(val_fname)) { parse_error(ctx, error, "include directive takes a string as parameter"); goto error; } fname = val_fname->data.string; if (!ctx->master_config) { parse_error(ctx, error, "include not allowed"); goto error; } if (NULL == strchr(fname->str, '*') && NULL == strchr(fname->str, '?')) { if (!include_file(list, fname, ctx, error)) goto error; } else { gchar *separator; GPatternSpec *pattern; gsize basedir_len; GDir *dir; const gchar *filename; GError *err = NULL; if (NULL == (separator = strrchr(fname->str, G_DIR_SEPARATOR))) { pattern = g_pattern_spec_new(fname->str); g_string_assign(fname, "."); } else { pattern = g_pattern_spec_new(separator + 1); g_string_truncate(fname, separator - fname->str); } dir = g_dir_open(fname->str, 0, &err); if (NULL == dir) { parse_error(ctx, error, "include couldn't open directory: %s", err->message); g_error_free(err); goto error; } g_string_append_c(fname, G_DIR_SEPARATOR); basedir_len = fname->len; /* loop through all filenames in the directory and include matching ones */ while (NULL != (filename = g_dir_read_name(dir))) { if (!g_pattern_match_string(pattern, filename)) continue; g_string_truncate(fname, basedir_len); g_string_append(fname, filename); if (!include_file(list, fname, ctx, error)) { g_pattern_spec_free(pattern); g_dir_close(dir); goto error; } } g_pattern_spec_free(pattern); g_dir_close(dir); } li_value_free(parameters); return TRUE; error: li_value_free(parameters); return FALSE; } static gboolean p_include_lua(liAction *list, liConfigTokenizerContext *ctx, GError **error) { liValue *parameters = NULL; liValue *val_fname; liAction *a; if (!p_parameter_values(¶meters, ctx, error)) return FALSE; val_fname = li_value_get_single_argument(parameters); if (NULL == val_fname || LI_VALUE_STRING != li_value_type(val_fname)) { li_value_free(parameters); return parse_error(ctx, error, "include_lua directive takes a string as parameter"); } if (!ctx->master_config) { li_value_free(parameters); return parse_error(ctx, error, "include_lua not allowed"); } #ifdef HAVE_LUA_H LI_FORCE_ASSERT(ctx->wrk == ctx->srv->main_worker); if (!li_config_lua_load(&ctx->srv->LL, ctx->srv, ctx->wrk, val_fname->data.string->str, &a, TRUE, NULL)) { parse_error(ctx, error, "include_lua '%s' failed", val_fname->data.string->str); li_value_free(parameters); return FALSE; } /* include lua doesn't need to produce an action */ if (NULL != a) { li_action_append_inplace(list, a); li_action_release(ctx->srv, a); } li_value_free(parameters); return TRUE; #else li_value_free(parameters); return parse_error(ctx, error, "compiled without lua, include_lua not supported"); #endif } static gboolean p_include_shell(liAction *list, liConfigTokenizerContext *ctx, GError **error) { liValue *parameters = NULL; liValue *val_command; GString *command; gchar *cmd_stdout = NULL; liConfigTokenizerContext subctx; gboolean result; if (!p_parameter_values(¶meters, ctx, error)) return FALSE; val_command = li_value_get_single_argument(parameters); if (NULL == val_command || LI_VALUE_STRING != li_value_type(val_command)) { parse_error(ctx, error, "include_shell directive takes a string as parameter"); goto error; } command = val_command->data.string; if (!ctx->master_config) { parse_error(ctx, error, "include_shell not allowed"); goto error; } { gint status; GError *err = NULL; if (!g_spawn_command_line_sync(command->str, &cmd_stdout, NULL, &status, &err)) { parse_error(ctx, error, "include_shell '%s' failed: %s", command->str, err->message); g_error_free(err); goto error; } if (0 != status) { parse_error(ctx, error, "include_shell '%s' retured non-zero status: %i", command->str, status); goto error; } } g_string_prepend_len(command, CONST_STR_LEN("shell: ")); tokenizer_init(ctx->srv, ctx->wrk, &subctx, command->str, cmd_stdout, strlen(cmd_stdout)); subctx.master_config = ctx->master_config; scope_enter(&subctx); subctx.current_scope->parent = ctx->current_scope; result = p_actions(FALSE, list, &subctx, error); scope_leave(&subctx); g_free((gchar*) subctx.content); tokenizer_clear(&subctx); li_value_free(parameters); return result; error: li_value_free(parameters); return FALSE; } static gboolean p_setup(GString *name, liConfigTokenizerContext *ctx, GError **error) { liValue *parameters = NULL; if (!p_parameter_values(¶meters, ctx, error)) return FALSE; if (g_str_equal(name->str, "__print")) { GString *s = li_value_to_string(parameters); DEBUG(ctx->srv, "config __print: %s", s->str); g_string_free(s, TRUE); li_value_free(parameters); return TRUE; } else if (!li_plugin_config_setup(ctx->srv, name->str, parameters)) { return parse_error(ctx, error, "setup '%s' failed", name->str); } return TRUE; } static gboolean p_setup_block(liConfigTokenizerContext *ctx, GError **error) { liConfigToken token; GString *name = NULL; NEXT(token); switch (token) { case TK_CURLY_CLOSE: return TRUE; case TK_NAME: name = g_string_new_len(GSTR_LEN(ctx->token_string)); NEXT(token); switch (token) { case TK_ASSIGN: if (!p_vardef(name, 0, ctx, error)) goto error; break; default: REMEMBER(token); if (!p_setup(name, ctx, error)) goto error; break; } g_string_free(name, TRUE); name = NULL; break; case TK_INCLUDE: case TK_INCLUDE_LUA: case TK_INCLUDE_SHELL: return parse_error(ctx, error, "include not supported in setup mode"); default: return parse_error(ctx, error, "expected name or block end }"); } return p_setup_block(ctx, error); error: if (NULL != name) g_string_free(name, TRUE); name = NULL; return FALSE; } static gboolean p_action(liAction *list, GString *name, liConfigTokenizerContext *ctx, GError **error) { liValue *parameters = NULL; liAction *a = NULL; liValue *alias; alias = scope_peekvar(ctx, name); if (NULL != alias) { liConfigToken token; if (li_value_type(alias) != LI_VALUE_ACTION) { return parse_error(ctx, error, "'%s' is not an action variable", name->str); } NEXT(token); if (token != TK_SEMICOLON) { return parse_error(ctx, error, "action alias '%s' doesn't take any parameter, expected ';'", name->str); } li_action_append_inplace(list, alias->data.val_action.action); return TRUE; error: return FALSE; } if (!p_parameter_values(¶meters, ctx, error)) return FALSE; if (g_str_equal(name->str, "__print")) { GString *s = li_value_to_string(parameters); DEBUG(ctx->srv, "config __print: %s", s->str); g_string_free(s, TRUE); li_value_free(parameters); return TRUE; } else if (NULL == (a = li_plugin_config_action(ctx->srv, ctx->wrk, name->str, parameters))) { return parse_error(ctx, error, "action '%s' failed", name->str); } li_action_append_inplace(list, a); li_action_release(ctx->srv, a); return TRUE; } /* parse actions until EOF (if !block) or '}' (if block, TK_CURLY_CLOSE) */ static gboolean p_actions(gboolean block, liAction *list, liConfigTokenizerContext *ctx, GError **error) { liConfigToken token; GString *name = NULL; NEXT(token); switch (token) { case TK_SETUP: if (!ctx->master_config) { return parse_error(ctx, error, "setup not allowed in this context"); } NEXT(token); switch (token) { case TK_CURLY_OPEN: if (!p_setup_block(ctx, error)) goto error; break; case TK_NAME: name = g_string_new_len(GSTR_LEN(ctx->token_string)); if (!p_setup(name, ctx, error)) goto error; break; case TK_INCLUDE: case TK_INCLUDE_LUA: case TK_INCLUDE_SHELL: return parse_error(ctx, error, "include not supported in setup mode"); default: return parse_error(ctx, error, "expected name or block start '{'"); } break; case TK_NAME: name = g_string_new_len(GSTR_LEN(ctx->token_string)); NEXT(token); switch (token) { case TK_ASSIGN: if (!p_vardef(name, 0, ctx, error)) goto error; break; default: REMEMBER(token); if (!p_action(list, name, ctx, error)) goto error; break; } g_string_free(name, TRUE); name = NULL; break; case TK_LOCAL: NEXT(token); if (TK_NAME != token) return parse_error(ctx, error, "expected variable name after 'local'"); name = g_string_new_len(GSTR_LEN(ctx->token_string)); NEXT(token); if (TK_ASSIGN != token) { parse_error(ctx, error, "expected '=' after variable name"); goto error; } if (!p_vardef(name, 1, ctx, error)) goto error; g_string_free(name, TRUE); name = NULL; break; case TK_GLOBAL: NEXT(token); if (TK_NAME != token) return parse_error(ctx, error, "expected variable name after 'global'"); name = g_string_new_len(GSTR_LEN(ctx->token_string)); NEXT(token); if (TK_ASSIGN != token) { parse_error(ctx, error, "expected '=' after variable name"); goto error; } if (!p_vardef(name, 2, ctx, error)) goto error; g_string_free(name, TRUE); name = NULL; break; case TK_INCLUDE: if (!p_include(list, ctx, error)) goto error; break; case TK_INCLUDE_LUA: if (!p_include_lua(list, ctx, error)) goto error; break; case TK_INCLUDE_SHELL: if (!p_include_shell(list, ctx, error)) goto error; break; case TK_IF: if (!p_condition(list, ctx, error)) goto error; break; case TK_EOF: if (block) return parse_error(ctx, error, "unexpected end of file, expected name or '}'"); return TRUE; case TK_CURLY_CLOSE: if (!block) return parse_error(ctx, error, "expected end of file instead of '}'"); return TRUE; default: return parse_error(ctx, error, "unexpected token; expected action"); } return p_actions(block, list, ctx, error); error: if (NULL != name) g_string_free(name, TRUE); name = NULL; return FALSE; } static gboolean p_value_list(gint *key_value_nesting, liValue **result, gboolean key_value_list, liValue *pre_value, liConfigToken end, liConfigTokenizerContext *ctx, GError **error) { liValue *list = li_value_new_list(); gint kv_nesting = *key_value_nesting; liValue *value = NULL, *key = NULL; for (;;) { gint sub_kv_nesting; liConfigToken token; if (NULL == pre_value) { NEXT(token); if (end == token) break; if (TK_COMMA == token) continue; REMEMBER(token); if (!p_value(&sub_kv_nesting, &value, TK_ERROR, ctx, error)) goto error; if (sub_kv_nesting < kv_nesting) kv_nesting = sub_kv_nesting; } else { value = pre_value; pre_value = NULL; } NEXT(token); if (!key_value_list && TK_ASSOCICATE == token) { key_value_list = TRUE; if (li_value_list_len(list) > 0) { parse_error(ctx, error, "unexpected '=>'"); goto error; } } if (key_value_list) { liValue *pair; if (TK_ASSOCICATE != token) { parse_error(ctx, error, "expected '=>'"); goto error; } key = value; value = NULL; if (!p_value(&sub_kv_nesting, &value, TK_ERROR, ctx, error)) goto error; pair = li_value_new_list(); li_value_list_append(pair, key); li_value_list_append(pair, value); li_value_list_append(list, pair); value = key = NULL; } else { REMEMBER(token); li_value_list_append(list, value); value = NULL; } } if (key_value_list) *key_value_nesting = 0; else *key_value_nesting = kv_nesting + 1; *result = list; return TRUE; error: li_value_free(list); li_value_free(value); li_value_free(key); return FALSE; } static gboolean p_value_group(gint *key_value_nesting, liValue **value, liConfigTokenizerContext *ctx, GError **error) { liValue *v = NULL; liConfigToken token; if (!p_value(key_value_nesting, &v, TK_ERROR, ctx, error)) return FALSE; NEXT(token); switch (token) { case TK_PARA_CLOSE: *value = v; return TRUE; case TK_COMMA: /* a list */ REMEMBER(token); return p_value_list(key_value_nesting, value, FALSE, v, TK_PARA_CLOSE, ctx, error); case TK_ASSOCICATE: /* a key-value list */ REMEMBER(token); return p_value_list(key_value_nesting, value, TRUE, v, TK_PARA_CLOSE, ctx, error); default: li_value_free(v); return parse_error(ctx, error, "expected ')'"); } error: li_value_free(v); return FALSE; } /* preOp: TK_ERROR - no operator before value * TK_NOT - unary operator before value (not, casts) */ static gboolean p_value(gint *key_value_nesting, liValue **value, liConfigToken preOp, liConfigTokenizerContext *ctx, GError **error) { liConfigToken token; liValue *v = NULL, *vcast; gint kv_nesting = KV_LISTING_MAX; NEXT(token); if (!is_value_start_token(token)) return parse_error(ctx, error, "expected value"); switch (token) { case TK_CAST_STRING: { GString *s; if (!p_value(&kv_nesting, &v, TK_NOT, ctx, error)) return FALSE; s = li_value_to_string(v); if (NULL == s) s = g_string_sized_new(0); li_value_free(v); v = li_value_new_string(s); break; } break; case TK_CAST_INT: if (!p_value(&kv_nesting, &v, TK_NOT, ctx, error)) return FALSE; switch (li_value_type(v)) { case LI_VALUE_NUMBER: break; /* nothing to do */ case LI_VALUE_BOOLEAN: vcast = v; v = li_value_new_number(vcast->data.boolean ? 1 : 0); li_value_free(vcast); break; case LI_VALUE_STRING: { GString *s = v->data.string; gchar *endptr = NULL; gint64 i; errno = 0; i = g_ascii_strtoll(s->str, &endptr, 10); if (errno != 0 || endptr != s->str + s->len || s->len == 0) { li_value_free(v); return parse_error(ctx, error, "cast(int) failed, not a valid number: '%s'", s->str); } li_value_free(v); v = li_value_new_number(i); } break; default: parse_error(ctx, error, "cast(int) from %s not supported yet", li_value_type_string(v)); li_value_free(v); return FALSE; } break; case TK_CURLY_OPEN: { liAction *alist = li_action_new_list(); scope_enter(ctx); if (!p_actions(TRUE, alist, ctx, error)) { scope_leave(ctx); li_action_release(ctx->srv, alist); return FALSE; } scope_leave(ctx); v = li_value_new_action(ctx->srv, alist); } break; case TK_DEFAULT: v = li_value_new_none(); break; case TK_FALSE: v = li_value_new_bool(FALSE); break; case TK_INTEGER: v = li_value_new_number(ctx->token_number); break; case TK_MINUS: if (!p_value(&kv_nesting, &v, TK_NOT, ctx, error)) return FALSE; if (li_value_type(v) == LI_VALUE_NUMBER) { if (v->data.number == G_MININT64) { li_value_free(v); return parse_error(ctx, error, "'-' overflow on minimum int value"); } v->data.number = -v->data.number; } else { li_value_free(v); return parse_error(ctx, error, "'-' only supported for integer values"); } break; case TK_NAME: v = scope_getvar(ctx, ctx->token_string, error); if (NULL == v) return FALSE; break; case TK_NONE: v = li_value_new_none(); break; case TK_NOT: if (!p_value(&kv_nesting, &v, TK_NOT, ctx, error)) return FALSE; if (li_value_type(v) == LI_VALUE_BOOLEAN) { v->data.boolean = !v->data.boolean; } else { li_value_free(v); return parse_error(ctx, error, "'not' only supported for boolean values"); } break; case TK_PARA_OPEN: if (!p_value_group(&kv_nesting, &v, ctx, error)) return FALSE; break; case TK_SQUARE_OPEN: if (!p_value_list(&kv_nesting, &v, FALSE, NULL, TK_SQUARE_CLOSE, ctx, error)) return FALSE; break; case TK_STRING: v = li_value_new_string(g_string_new_len(GSTR_LEN(ctx->token_string))); break; case TK_TRUE: v = li_value_new_bool(TRUE); break; default: /* is_value_start_token should have been false */ return parse_error(ctx, error, "internal error: unsupported value type"); } if (preOp == TK_NOT) { /* had unary operator before it */ *value = v; *key_value_nesting = kv_nesting; return TRUE; } for (;;) { liValue *v2 = NULL, *vresult = NULL; gint sub_kv_nesting = KV_LISTING_MAX; NEXT(token); if (operator_precedence(preOp, token)) { REMEMBER(token); *value = v; *key_value_nesting = kv_nesting; return TRUE; } if (!p_value(&sub_kv_nesting, &v2, token, ctx, error)) { li_value_free(v); return FALSE; } if (sub_kv_nesting < kv_nesting) kv_nesting = sub_kv_nesting; if (!op_execute(&vresult, token, v, v2, ctx, error)) return FALSE; v = vresult; } error: li_value_free(v); return FALSE; } static gboolean p_parameter_values(liValue **result, liConfigTokenizerContext *ctx, GError **error) { gint key_value_nesting = KV_LISTING_MAX; liValue *value = NULL; liConfigToken token; NEXT(token); if (token == TK_SEMICOLON) { *result = NULL; return TRUE; } REMEMBER(token); if (!p_value(&key_value_nesting, &value, TK_ERROR, ctx, error)) return FALSE; NEXT(token); switch (token) { case TK_SEMICOLON: break; case TK_COMMA: /* a list */ REMEMBER(token); if (!p_value_list(&key_value_nesting, &value, FALSE, value, TK_SEMICOLON, ctx, error)) return FALSE; break; case TK_ASSOCICATE: /* a key-value list */ REMEMBER(token); if (!p_value_list(&key_value_nesting, &value, TRUE, value, TK_SEMICOLON, ctx, error)) return FALSE; break; default: li_value_free(value); return parse_error(ctx, error, "expected ';'"); } /* goal: return a list of parameters. a key-value list should be interpreted as a single value, and not * get split into parameters. */ switch (key_value_nesting) { case 0: /* outer key-value list, wrap once */ li_value_wrap_in_list(value); break; default: /* always have a list of parameters */ if (LI_VALUE_LIST != li_value_type(value)) li_value_wrap_in_list(value); break; } /* { GString *s = li_value_to_string(value); DEBUG(ctx->srv, "parameters: %s", s->str); g_string_free(s, TRUE); } DEBUG(ctx->srv, "nested kv: %i", key_value_nesting); */ *result = value; return TRUE; error: li_value_free(value); return FALSE; } static gboolean p_vardef(GString *name, int normalLocalGlobal, liConfigTokenizerContext *ctx, GError **error) { liValue *value = NULL; gint key_value_nesting; liConfigToken token; if (!p_value(&key_value_nesting, &value, TK_ERROR, ctx, error)) return FALSE; NEXT(token); if (TK_SEMICOLON != token) { li_value_free(value); return parse_error(ctx, error, "expected ';'"); } switch (normalLocalGlobal) { case 0: return scope_setvar(ctx, name, value, error); case 1: return scope_local_setvar(ctx, name, value, error); case 2: return scope_global_setvar(ctx, name, value, error); default: LI_FORCE_ASSERT(normalLocalGlobal >= 0 && normalLocalGlobal <= 2); } error: li_value_free(value); return FALSE; } static liAction* cond_walk(liServer *srv, liConditionTree *tree, liAction *positive, liAction *negative) { liAction *a = NULL; LI_FORCE_ASSERT(NULL != tree); if (tree->negated) { liAction *tmp = negative; negative = positive; positive = tmp; } if (NULL != tree->condition) { LI_FORCE_ASSERT(NULL == tree->left && NULL == tree->right); if (NULL == positive && NULL == negative) { li_condition_release(srv, tree->condition); } else { a = li_action_new_condition(tree->condition, positive, negative); } } else switch (tree->op) { case TK_AND: if (NULL != negative) li_action_acquire(negative); a = cond_walk(srv, tree->left, cond_walk(srv, tree->right, positive, negative), negative); break; case TK_OR: if (NULL != positive) li_action_acquire(positive); a = cond_walk(srv, tree->left, positive, cond_walk(srv, tree->right, positive, negative)); break; default: LI_FORCE_ASSERT(TK_AND == tree->op || TK_OR == tree->op); } g_slice_free(liConditionTree, tree); return a; } static gboolean p_condition_value(liConditionTree **tree, liConfigTokenizerContext *ctx, GError **error) { liConfigToken token; liCondLValue lval; liConditionLValue *lvalue = NULL; gint key_value_nesting = -1; liValue *rvalue = NULL; liCondition *cond = NULL; liCompOperator compop; NEXT(token); if (TK_NAME != token) return parse_error(ctx, error, "expected a condition variable"); lval = li_cond_lvalue_from_string(GSTR_LEN(ctx->token_string)); if (LI_COMP_UNKNOWN == lval) return parse_error(ctx, error, "unknown condition variable: %s", ctx->token_string->str); if (LI_COMP_REQUEST_HEADER == lval || LI_COMP_ENVIRONMENT == lval || LI_COMP_RESPONSE_HEADER == lval) { NEXT(token); if (TK_SQUARE_OPEN != token) return parse_error(ctx, error, "condition variable %s requires a key", li_cond_lvalue_to_string(lval)); NEXT(token); if (TK_STRING != token) return parse_error(ctx, error, "expected a string as key to condition variable"); lvalue = li_condition_lvalue_new(lval, g_string_new_len(GSTR_LEN(ctx->token_string))); NEXT(token); if (TK_SQUARE_CLOSE != token) { parse_error(ctx, error, "expected ']'"); goto error; } } else { NEXT(token); if (TK_SQUARE_OPEN == token) return parse_error(ctx, error, "condition variable %s doesn't use a key", li_cond_lvalue_to_string(lval)); REMEMBER(token); lvalue = li_condition_lvalue_new(lval, NULL); } NEXT(token); switch (token) { case TK_AND: case TK_OR: case TK_PARA_CLOSE: case TK_CURLY_OPEN: REMEMBER(token); cond = li_condition_new_bool(ctx->srv, lvalue, TRUE); goto boolcond; case TK_EQUAL: compop = LI_CONFIG_COND_EQ; break; case TK_GREATER: compop = LI_CONFIG_COND_GT; break; case TK_GREATER_EQUAL: compop = LI_CONFIG_COND_GE; break; case TK_LESS: compop = LI_CONFIG_COND_LT; break; case TK_LESS_EQUAL: compop = LI_CONFIG_COND_LE; break; case TK_MATCH: compop = LI_CONFIG_COND_MATCH; break; case TK_NOT_EQUAL: compop = LI_CONFIG_COND_NE; break; case TK_NOT_MATCH: compop = LI_CONFIG_COND_NOMATCH; break; case TK_NOT_PREFIX: compop = LI_CONFIG_COND_NOPREFIX; break; case TK_NOT_SUBNET: compop = LI_CONFIG_COND_NOTIP; break; case TK_NOT_SUFFIX: compop = LI_CONFIG_COND_NOSUFFIX; break; case TK_PREFIX: compop = LI_CONFIG_COND_PREFIX; break; case TK_SUBNET: compop = LI_CONFIG_COND_IP; break; case TK_SUFFIX: compop = LI_CONFIG_COND_SUFFIX; break; default: parse_error(ctx, error, "expected comparison operator"); goto error; } if (!p_value(&key_value_nesting, &rvalue, TK_NOT, ctx, error)) goto error; switch (li_value_type(rvalue)) { case LI_VALUE_STRING: cond = li_condition_new_string(ctx->srv, compop, lvalue, li_value_extract_string(rvalue)); break; case LI_VALUE_NUMBER: cond = li_condition_new_int(ctx->srv, compop, lvalue, rvalue->data.number); break; default: parse_error(ctx, error, "wrong value type, require string or integer"); goto error; } li_value_free(rvalue); boolcond: *tree = g_slice_new0(liConditionTree); (*tree)->negated = FALSE; (*tree)->condition = cond; return TRUE; error: li_condition_lvalue_release(lvalue); li_value_free(rvalue); return FALSE; } static gboolean p_condition_expr(liConditionTree **tree, liConfigToken preOp, liConfigTokenizerContext *ctx, GError **error) { liConditionTree *leave = NULL; liConditionTree *result = NULL; liConfigToken token; liConfigToken op = TK_ERROR; for (;;) { gboolean negated = FALSE; NEXT(token); if (TK_NOT == token) { negated = TRUE; NEXT(token); } if (TK_PARA_OPEN == token) { if (!p_condition_expr(&leave, TK_ERROR, ctx, error)) goto error; NEXT(token); if (TK_PARA_CLOSE != token) { parse_error(ctx, error, "expected ')'"); goto error; } } else { REMEMBER(token); if (!p_condition_value(&leave, ctx, error)) goto error; } if (negated) leave->negated = !leave->negated; if (result == NULL) { result = leave; } else { liConditionTree *tmp = g_slice_new0(liConditionTree); tmp->negated = FALSE; tmp->op = op; tmp->left = result; tmp->right = leave; result = tmp; } leave = NULL; NEXT(token); switch (token) { case TK_PARA_CLOSE: case TK_CURLY_OPEN: REMEMBER(token); *tree = result; return TRUE; case TK_AND: case TK_OR: if (operator_precedence(preOp, token)) { REMEMBER(token); *tree = result; return TRUE; } op = token; break; default: parse_error(ctx, error, "unexpected token in conditional"); goto error; } } error: if (NULL != result) cond_walk(ctx->srv, result, NULL, NULL); return FALSE; } static gboolean p_condition(liAction *list, liConfigTokenizerContext *ctx, GError **error) { liConditionTree *tree = NULL; liConfigToken token; liAction *positive = NULL, *negative = NULL; if (!p_condition_expr(&tree, TK_ERROR, ctx, error)) return FALSE; NEXT(token); if (TK_CURLY_OPEN != token) { parse_error(ctx, error, "expected '{'"); goto error; } positive = li_action_new(); if (!p_actions(TRUE, positive, ctx, error)) goto error; NEXT(token); if (TK_ELSE == token) { negative = li_action_new(); NEXT(token); switch (token) { case TK_CURLY_OPEN: if (!p_actions(TRUE, negative, ctx, error)) goto error; break; case TK_IF: if (!p_condition(negative, ctx, error)) goto error; break; default: parse_error(ctx, error, "expected '{' or 'if' after 'else'"); goto error; } } else { REMEMBER(token); } { liAction *a = cond_walk(ctx->srv, tree, positive, negative); li_action_append_inplace(list, a); li_action_release(ctx->srv, a); } return TRUE; error: if (NULL != tree) cond_walk(ctx->srv, tree, NULL, NULL); li_action_release(ctx->srv, positive); li_action_release(ctx->srv, negative); return FALSE; } gboolean li_config_parse(liServer *srv, const gchar *config_path) { liConfigTokenizerContext ctx; GError *error = NULL; gboolean result; if (!tokenizer_init_file(srv, srv->main_worker, &ctx, config_path, &error)) { ERROR(srv, "%s", error->message); g_error_free(error); return FALSE; } ctx.master_config = TRUE; srv->mainaction = li_action_new(); scope_enter(&ctx); result = p_actions(FALSE, srv->mainaction, &ctx, &error); scope_leave(&ctx); g_free((gchar*) ctx.content); tokenizer_clear(&ctx); if (!result) { ERROR(srv, "config error: %s", error->message); g_error_free(error); return FALSE; } { liAction *a_static = li_plugin_config_action(srv, srv->main_worker, "static", NULL); if (NULL == a_static) { ERROR(srv, "%s", "config error: couldn't create static action"); return FALSE; } li_action_append_inplace(srv->mainaction, a_static); li_action_release(srv, a_static); } return TRUE; } liAction* li_config_parse_live(liWorker *wrk, const gchar *sourcename, const char *source, gsize sourcelen, GError **error) { liAction *list = li_action_new(); liConfigTokenizerContext ctx; gboolean result; tokenizer_init(wrk->srv, wrk, &ctx, sourcename, source, sourcelen); ctx.master_config = FALSE; scope_enter(&ctx); result = p_actions(FALSE, list, &ctx, error); scope_leave(&ctx); tokenizer_clear(&ctx); if (!result) { li_action_release(wrk->srv, list); list = NULL; } return list; }