diff --git a/parser/Makefile b/parser/Makefile index d18ffaa71..849dacae9 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -105,12 +105,12 @@ SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \ parser_alias.c common_optarg.c lib.c network.cc \ mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \ af_rule.cc af_unix.cc policy_cache.c default_features.c userns.cc \ - mqueue.cc io_uring.cc all_rule.cc cond_expr.cc + mqueue.cc io_uring.cc all_rule.cc cond_expr.cc variable.cc symtab.cc STATIC_HDRS = af_rule.h af_unix.h capability.h common_optarg.h dbus.h \ file_cache.h immunix.h lib.h mount.h network.h parser.h \ parser_include.h parser_version.h policy_cache.h policydb.h \ profile.h ptrace.h rule.h signal.h userns.h mqueue.h io_uring.h \ - common_flags.h bignum.h all_rule.h cond_expr.h + common_flags.h bignum.h all_rule.h cond_expr.h variable.h symtab.h SPECIAL_HDRS = parser_yacc.h unit_test.h base_cap_names.h GENERATED_HDRS = af_names.h generated_af_names.h \ @@ -331,6 +331,13 @@ all_rule.o: all_rule.cc $(HDRS) cond_expr.o: cond_expr.cc $(HDRS) $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< +variable.o: variable.cc $(HDRS) + $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< + +symtab.o: symtab.cc $(HDRS) + $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< + + parser_version.h: Makefile @echo \#define PARSER_VERSION \"$(VERSION)\" > .ver @mv -f .ver $@ diff --git a/parser/cond_expr.cc b/parser/cond_expr.cc index 2a7c6ef94..f601618a1 100644 --- a/parser/cond_expr.cc +++ b/parser/cond_expr.cc @@ -18,6 +18,7 @@ #include "cond_expr.h" #include "parser.h" +#include "symtab.h" cond_expr::cond_expr(bool result): result(result) @@ -26,20 +27,21 @@ cond_expr::cond_expr(bool result): cond_expr::cond_expr(const char *var, bool defined) { - char *var_name = process_var(var); - + variable *ref; if (!defined) { - int ret = get_boolean_var(var_name); - if (ret < 0) { + ref = symtab::get_boolean_var(var); + if (!ref) { /* FIXME check for set var */ - free(var_name); yyerror(_("Unset boolean variable %s used in if-expression"), var); } - result = ret; + result = ref->boolean; } else { - void *set_value = get_set_var(var_name); - PDEBUG("Matched: defined set expr %s value %lx\n", var_name, (long) set_value); - result = !! (long) set_value; + ref = symtab::get_set_var(var); + if (!ref) { + result = false; + } else { + PDEBUG("Matched: defined set expr %s value %s\n", var, ref->expanded.begin()->c_str()); + result = true; + } } - free(var_name); } diff --git a/parser/parser.h b/parser/parser.h index 3e851205f..13b83bbcd 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -415,8 +415,6 @@ extern const char *local_name(const char *name); /* parser_variable.c */ int expand_entry_variables(char **name); extern int process_variables(Profile *prof); -extern struct var_string *split_out_var(const char *string); -extern void free_var_string(struct var_string *var); /* parser_misc.c */ extern void warn_uppercase(void); @@ -458,38 +456,6 @@ bool entry_add_prefix(struct cod_entry *entry, const prefixes &p, const char *&e #define SECONDS_P_MS (1000LL * 1000LL) long long convert_time_units(long long value, long long base, const char *units); - -/* parser_symtab.c */ -struct set_value { - char *val; - struct set_value *next; -}; -enum var_type { - sd_boolean, - sd_set, -}; - -struct symtab { - char *var_name; - enum var_type type; - int boolean; - struct set_value *values; - struct set_value *expanded; -}; - -extern int add_boolean_var(const char *var, int boolean); -extern int get_boolean_var(const char *var); -extern int new_set_var(const char *var, const char *value); -extern int add_set_value(const char *var, const char *value); -extern struct set_value *get_set_var(const char *var); -extern char *get_next_set_value(struct set_value **context); -extern int insert_set_var(struct symtab *var); -extern struct symtab *remove_set_var(const char *var_name); -extern int delete_set_var(const char *var_name); -extern void dump_symtab(void); -extern void dump_expanded_symtab(void); -void free_symtabs(void); - /* parser_alias.c */ extern int new_alias(const char *from, const char *to); extern int replace_profile_aliases(Profile *prof); diff --git a/parser/parser_main.c b/parser/parser_main.c index 802217dd1..960a688b7 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -51,6 +51,7 @@ #include "policy_cache.h" #include "libapparmor_re/apparmor_re.h" #include "file_cache.h" +#include "symtab.h" #define OLD_MODULE_NAME "subdomain" #define PROC_MODULES "/proc/modules" @@ -1092,7 +1093,7 @@ void reset_parser(const char *filename) memset(&cache_tstamp, 0, sizeof(cache_tstamp)); mru_skip_cache = 1; free_aliases(); - free_symtabs(); + symtab::free_symtab(); free_policies(); reset_include_stack(filename); aa_features_unref(policy_features); @@ -1223,7 +1224,7 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, } if (dump_vars) { - dump_symtab(); + symtab::dump(false); goto out; } @@ -1234,7 +1235,7 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, } if (dump_expanded_vars) { - dump_expanded_symtab(); + symtab::dump(true); goto out; } diff --git a/parser/parser_misc.c b/parser/parser_misc.c index 6f3f40895..4634b3f43 100644 --- a/parser/parser_misc.c +++ b/parser/parser_misc.c @@ -532,35 +532,6 @@ char *processid(const char *string, int len) return processunquoted(string, len); } -/* strip off surrounding delimiters around variables */ -char *process_var(const char *var) -{ - const char *orig = var; - int len = strlen(var); - - if (*orig == '@' || *orig == '$') { - orig++; - len--; - } else { - PERROR("ASSERT: Found var '%s' without variable prefix\n", - var); - return NULL; - } - - if (*orig == '{') { - orig++; - len--; - if (orig[len - 1] != '}') { - PERROR("ASSERT: No matching '}' in variable '%s'\n", - var); - return NULL; - } else - len--; - } - - return strndup(orig, len); -} - /* returns -1 if value != true or false, otherwise 0 == false, 1 == true */ int str_to_boolean(const char *value) { diff --git a/parser/parser_symtab.c b/parser/parser_symtab.c index 8f744f913..10d960b30 100644 --- a/parser/parser_symtab.c +++ b/parser/parser_symtab.c @@ -24,654 +24,27 @@ #include "immunix.h" #include "parser.h" - -typedef int (*comparison_fn_t)(const void *, const void *); -typedef void (*__free_fn_t)(void *); - - -static void *my_symtab = NULL; - -static int __expand_variable(struct symtab *symbol); - -static struct symtab *new_symtab_entry(const char *name) -{ - struct symtab *n = (struct symtab *) calloc(1, sizeof(*n)); - - if (!n) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - return NULL; - } - - n->var_name = strndup(name, PATH_MAX); - if (!n->var_name) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - free(n); - return NULL; - } - - return n; -} - -static struct set_value *new_set_value(const char *val) -{ - struct set_value *n = (struct set_value *) calloc(1, sizeof(*n)); - - if (!n) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - return NULL; - } - - n->val = strndup(val, PATH_MAX); - if (!n->val) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - free(n); - return NULL; - } - - return n; -} - -static void free_values(struct set_value *val) -{ - struct set_value *i = val, *tmp; - - while (i) { - if (i->val) - free(i->val); - tmp = i; - i = i->next; - free(tmp); - } -} - -static void free_symtab(struct symtab *symtab) -{ - if (!symtab) - return; - - if (symtab->var_name) - free(symtab->var_name); - - free_values(symtab->values); - free_values(symtab->expanded); - free(symtab); -} - -/* abstract this out in case we switch data structures */ -static void add_to_set(struct set_value **list, const char *val) -{ - struct set_value *new_item = new_set_value(val); - - new_item->next = *list; - *list = new_item; -} - -static int compare_symtabs(const void *a, const void *b) -{ - char *a_name = ((struct symtab *) a)->var_name; - char *b_name = ((struct symtab *) b)->var_name; - return strcmp(a_name, b_name); -} - -static struct symtab *lookup_existing_symbol(const char *var) -{ - struct symtab *tmp, **lookup; - struct symtab *result = NULL; - - tmp = new_symtab_entry(var); - if (!tmp) { - goto out; - } - - lookup = (struct symtab **) tfind(tmp, &my_symtab, (comparison_fn_t) &compare_symtabs); - if (!lookup) { - goto out; - } - - result = (*lookup); - -out: - free_symtab(tmp); - return result; - -} - -/* add_boolean_var - * creates copies of arguments, so caller can free them after use - */ - -int add_boolean_var(const char *var, int value) -{ - struct symtab *n, **result; - int rc = 0; - - n = new_symtab_entry(var); - if (!n) { - rc = ENOMEM; - goto err; - } - - n->type = sd_boolean; - n->boolean = value; - - result = (struct symtab **) tsearch(n, &my_symtab, (comparison_fn_t) &compare_symtabs); - if (!result) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - rc = errno; - goto err; - } - - if (*result != n) { - /* already existing variable */ - PERROR("'%s' is already defined\n", var); - rc = 1; - goto err; - } - - return 0; - -err: - free_symtab(n); - return rc; -}; - -int get_boolean_var(const char *var) -{ - struct symtab *result; - int rc = 0; - - result = lookup_existing_symbol(var); - if (!result) { - rc = -1; - goto out; - } - - if (result->type != sd_boolean) { - PERROR("Variable %s is not a boolean variable\n", var); - rc = -2; /* XXX - might change this to specific values */ - goto out; - } - - rc = result->boolean; -out: - return rc; -} - - -int insert_set_var(struct symtab *var) -{ - struct symtab **result; - - result = (struct symtab **) tsearch(var, &my_symtab, (comparison_fn_t) &compare_symtabs); - if (!result) { - PERROR("Failed to allocate memory: %s\n", strerror(errno)); - return errno; - } - - if (*result != var) { - /* already existing variable */ - PERROR("'%s' is already defined\n", var->var_name); - return 1; - } - - return 0; -} - -/* new_set_var - * creates copies of arguments, so caller can free them after use - */ -int new_set_var(const char *var, const char *value) -{ - struct symtab *n; - int rc = 0; - - n = new_symtab_entry(var); - if (!n) { - rc = ENOMEM; - goto err; - } - - n->type = sd_set; - add_to_set(&(n->values), value); - - rc = insert_set_var(n); - if (! rc) - return 0; - -err: - free_symtab(n); - return rc; -} - - -/* add_set_value - * creates copies of arguments, so caller can free them after use - */ -int add_set_value(const char *var, const char *value) -{ - struct symtab *result; - int rc = 0; - - result = lookup_existing_symbol(var); - if (!result) { - PERROR("Failed to find declaration for: %s\n", var); - rc = 1; - goto out; - } - - if (result->type != sd_set) { - PERROR("Variable %s is not a set variable\n", var); - rc = 2; /* XXX - might change this to specific values */ - goto out; - } - - if (strcmp(result->var_name, var) != 0) { - PERROR("ASSERT: tfind found %s when looking up variable %s\n", - result->var_name, var); - exit(1); - } - - add_to_set(&(result->values), value); - -out: - return rc; -} - -/* returns a pointer to the value list, which should be used as the - * argument to the get_next_set_value() function. */ -struct set_value *get_set_var(const char *var) -{ - struct symtab *result; - struct set_value *valuelist = NULL; - - result = lookup_existing_symbol(var); - if (!result) { - goto out; - } - - if (result->type != sd_set) { - goto out; - } - - if (strcmp(result->var_name, var) != 0) { - PERROR("ASSERT: tfind found %s when looking up variable %s\n", - result->var_name, var); - exit(1); - } - - if (!result->expanded) { - int err = __expand_variable(result); - if (err) { - PERROR("failure expanding variable %s\n", var); - exit(1); - } - } - valuelist = result->expanded; -out: - return valuelist; -} - -/* iterator to walk the list of set values */ -char *get_next_set_value(struct set_value **list) -{ - struct set_value *next; - char *ret; - - if (!list || !(*list)) - return NULL; - - ret = (*list)->val; - next = (*list)->next; - (*list) = next; - - return ret; -} - - -struct symtab *remove_set_var(const char *var_name) -{ - struct symtab **result, *n, *var = NULL; - - n = new_symtab_entry(var_name); - if (!n) { - //rc = ENOMEM; - goto out; - } - - result = (struct symtab **) tfind(n, &my_symtab, (comparison_fn_t) &compare_symtabs); - if (!result) { - /* XXX Warning? */ - //rc = ENOENT; - goto out; - } - - var = (*result); - - result = (struct symtab **) tdelete(n, &my_symtab, (comparison_fn_t) &compare_symtabs); - if (!result) { - PERROR("ASSERT: delete_set_var: tfind found var %s but tdelete failed to delete it\n", - var_name); - exit(1); - } - - if (var->type != sd_set) { - PERROR("ASSERT: delete_set_var: deleting %s but is a boolean variable\n", - var_name); - exit(1); - } - -out: - free_symtab(n); - - return var; -} - -/* delete_symbol - * removes an individual variable from the symbol table. We don't - * support this in the language, but for special variables that change - * between profiles, we need this. - */ -int delete_set_var(const char *var_name) -{ - struct symtab *var; - - var = remove_set_var(var_name); - if (var) { - free_symtab(var); - return 0; - } - return ENOENT; -} - -static void *seenlist = NULL; - -static int is_seen(const char *var) -{ - char **lookup; - lookup = (char **) tfind(var, &seenlist, (comparison_fn_t) &strcmp); - return (lookup != NULL); -} - -static void push_seen_var(const char *var) -{ - char **lookup; - lookup = (char **) tsearch(var, &seenlist, (comparison_fn_t) &strcmp); - if (*lookup != var) { - PERROR("ASSERT: '%s' is already in the seenlist\n", var); - exit(1); - } -} - -static void pop_seen_var(const char *var) -{ - char **lookup; - lookup = (char **) tdelete(var, &seenlist, (comparison_fn_t) &strcmp); - if (lookup == NULL) { - PERROR("ASSERT: popped var '%s' not found on the seenlist\n", var); - exit(1); - } -} - -static int __expand_variable(struct symtab *symbol) -{ - struct set_value *list, *expanded = NULL; - int retval = 0; - struct var_string *split = NULL; - - if (symbol->type == sd_boolean) { - PERROR("Referenced variable %s is a boolean used in set context\n", - symbol->var_name); - return 2; - } - - /* already done */ - if (symbol->expanded) - return 0; - - push_seen_var(symbol->var_name); - - for (list = symbol->values; list; list = list->next) { - struct set_value *work_list = new_set_value(list->val); - while (work_list) { - struct symtab *ref; - struct set_value *ref_item; - struct set_value *t_value = work_list; - int rc; - - work_list = work_list->next; - - split = split_out_var(t_value->val); - if (!split) { - /* fully expanded */ - add_to_set(&expanded, t_value->val); - goto next; - } - - - if (is_seen(split->var)) { - PERROR("Variable @{%s} is referenced recursively (by @{%s})\n", - split->var, symbol->var_name); - retval = 1; - free_values(t_value); - goto out; - } - - ref = lookup_existing_symbol(split->var); - if (!ref) { - PERROR("Variable @{%s} references undefined variable @{%s}\n", - symbol->var_name, split->var); - retval = 3; - free_values(t_value); - goto out; - } - - rc = __expand_variable(ref); - if (rc != 0) { - retval = rc; - free_values(t_value); - goto out; - } - - if (!ref->expanded) { - PERROR("ASSERT: Variable @{%s} should have been expanded but isn't\n", - split->var); - exit(1); - } - - for (ref_item = ref->expanded; ref_item; ref_item = ref_item->next) { - char *expanded_string; - if (!asprintf(&expanded_string, "%s%s%s", - split->prefix ? split->prefix : "", - ref_item->val, - split->suffix ? split->suffix : "")) { - PERROR("Out of memory\n"); - exit(1); - } - add_to_set(&work_list, expanded_string); - free(expanded_string); - } - -next: - t_value->next = NULL; - free_values(t_value); - free_var_string(split); - } - } - - symbol->expanded = expanded; - -out: - pop_seen_var(symbol->var_name); - free_var_string(split); - return retval; -} - -static void expand_variable(const void *nodep, VISIT value, int level unused) -{ - struct symtab **t = (struct symtab **) nodep; - - if (value == preorder || value == endorder) - return; - - if ((*t)->type == sd_boolean) - return; - - __expand_variable(*t); -} - -void expand_variables(void) -{ - twalk(my_symtab, &expand_variable); -} - -static inline void dump_set_values(struct set_value *value) -{ - struct set_value *t = value; - while (t) { - printf(" \"%s\"", t->val); - t = t->next; - } -} - -static void __dump_symtab_entry(struct symtab *entry, int do_expanded) -{ - switch (entry->type) { - case sd_boolean: - printf("$%s = %s\n", entry->var_name, - entry->boolean ? "true" : "false"); - break; - case sd_set: - printf("@%s =", entry->var_name); - if (do_expanded) { - if (!entry->expanded) { - __expand_variable(entry); - } - dump_set_values(entry->expanded); - } else { - dump_set_values(entry->values); - } - printf("\n"); - break; - default: - PERROR("ASSERT: unknown symbol table type for %s\n", entry->var_name); - exit(1); - } -} - -static void dump_symtab_entry(const void *nodep, VISIT value, int level unused) -{ - struct symtab **t = (struct symtab **) nodep; - - if (value == preorder || value == endorder) - return; - - __dump_symtab_entry(*t, 0); -} - -static void dump_expanded_symtab_entry(const void *nodep, VISIT value, int level unused) -{ - struct symtab **t = (struct symtab **) nodep; - - if (value == preorder || value == endorder) - return; - - __dump_symtab_entry(*t, 1); -} - -void dump_symtab(void) -{ - twalk(my_symtab, &dump_symtab_entry); -} - -void dump_expanded_symtab(void) -{ - twalk(my_symtab, &dump_expanded_symtab_entry); -} - -void free_symtabs(void) -{ - if (my_symtab) - tdestroy(my_symtab, (__free_fn_t)&free_symtab); - my_symtab = NULL; -} +#include "symtab.h" #ifdef UNIT_TEST #include "unit_test.h" -int test_compare_symtab(void) -{ - int rc = 0; - int retval; - struct symtab *a, *b, *c; - - a = new_symtab_entry("blah"); - b = new_symtab_entry("suck"); - MY_TEST(a && b, "allocation test"); - - retval = compare_symtabs(a, b); - MY_TEST(retval < 0, "comparison 1"); - - retval = compare_symtabs(b, a); - MY_TEST(retval > 0, "comparison 2"); - - retval = compare_symtabs(b, a); - MY_TEST(retval != 0, "comparison 3"); - - retval = compare_symtabs(b, b); - MY_TEST(retval == 0, "comparison 4"); - - c = new_symtab_entry("blah"); - retval = compare_symtabs(a, c); - MY_TEST(retval == 0, "comparison 5"); - - free_symtab(a); - free_symtab(b); - free_symtab(c); - - return rc; -} - -int test_seenlist(void) -{ - int rc = 0; - - MY_TEST(!is_seen("oogabooga"), "lookup unseen variable"); - - push_seen_var("oogabooga"); - MY_TEST(is_seen("oogabooga"), "lookup seen variable 1"); - MY_TEST(!is_seen("not_seen"), "lookup unseen variable 2"); - - push_seen_var("heebiejeebie"); - MY_TEST(is_seen("oogabooga"), "lookup seen variable 2"); - MY_TEST(is_seen("heebiejeebie"), "lookup seen variable 3"); - MY_TEST(!is_seen("not_seen"), "lookup unseen variable 3"); - - pop_seen_var("oogabooga"); - MY_TEST(!is_seen("oogabooga"), "lookup unseen variable 4"); - MY_TEST(is_seen("heebiejeebie"), "lookup seen variable 4"); - MY_TEST(!is_seen("not_seen"), "lookup unseen variable 5"); - - pop_seen_var("heebiejeebie"); - MY_TEST(!is_seen("heebiejeebie"), "lookup unseen variable 6"); - - //pop_seen_var("not_seen"); /* triggers assert */ - - return rc; -} - int test_add_set_to_boolean(void) { int rc = 0; int retval; - + struct value_list *val; /* test adding a set value to a boolean variable */ - retval = add_boolean_var("not_a_set_variable", 1); + retval = symtab::add_var("@not_a_set_variable", 1); MY_TEST(retval == 0, "new boolean variable 3"); - retval = add_set_value("not_a_set_variable", "a set value"); + + val = new_value_list(strdup("a set value")); + retval = symtab::add_set_value("@not_a_set_variable", val); MY_TEST(retval != 0, "add set value to boolean"); - free_symtabs(); + symtab::free_symtab(); + free_value_list(val); return rc; } @@ -680,19 +53,19 @@ int test_expand_bool_within_set(void) { int rc = 0; int retval; - struct symtab *retsym; + variable *retsym; /* test expanding a boolean var within a set variable */ - retval = add_boolean_var("not_a_set_variable", 1); + retval = symtab::add_var("@not_a_set_variable", 1); MY_TEST(retval == 0, "new boolean variable 4"); - retval = new_set_var("set_variable", "set_value@{not_a_set_variable}"); + retval = symtab::add_var("set_variable", "set_value@{not_a_set_variable}"); MY_TEST(retval == 0, "add set value with embedded boolean"); - retsym = lookup_existing_symbol("set_variable"); + retsym = symtab::lookup_existing_symbol("set_variable"); MY_TEST(retsym != NULL, "get set variable w/boolean"); - retval = __expand_variable(retsym); + retval = retsym->expand_variable(); MY_TEST(retval != 0, "expand set variable with embedded boolean"); - free_symtabs(); + symtab::free_symtab(); return rc; } @@ -701,21 +74,21 @@ int test_expand_recursive_set_vars(void) { int rc = 0; int retval; - struct symtab *retsym; + variable *retsym; /* test expanding a recursive var within a set variable */ - retval = new_set_var("recursive_1", "set_value@{recursive_2}"); + retval = symtab::add_var("recursive_1", "set_value@{recursive_2}"); MY_TEST(retval == 0, "new recursive set variable 1"); - retval = new_set_var("recursive_2", "set_value@{recursive_3}"); + retval = symtab::add_var("recursive_2", "set_value@{recursive_3}"); MY_TEST(retval == 0, "new recursive set variable 2"); - retval = new_set_var("recursive_3", "set_value@{recursive_1}"); + retval = symtab::add_var("recursive_3", "set_value@{recursive_1}"); MY_TEST(retval == 0, "new recursive set variable 3"); - retsym = lookup_existing_symbol("recursive_1"); + retsym = symtab::lookup_existing_symbol("recursive_1"); MY_TEST(retsym != NULL, "get recursive set variable"); - retval = __expand_variable(retsym); + retval = retsym->expand_variable(); MY_TEST(retval != 0, "expand recursive set variable"); - free_symtabs(); + symtab::free_symtab(); return rc; } @@ -724,17 +97,17 @@ int test_expand_undefined_set_var(void) { int rc = 0; int retval; - struct symtab *retsym; + variable *retsym; /* test expanding an undefined var within a set variable */ - retval = new_set_var("defined_var", "set_value@{undefined_var}"); + retval = symtab::add_var("defined_var", "set_value@{undefined_var}"); MY_TEST(retval == 0, "new undefined test set variable"); - retsym = lookup_existing_symbol("defined_var"); + retsym = symtab::lookup_existing_symbol("defined_var"); MY_TEST(retsym != NULL, "get undefined test set variable"); - retval = __expand_variable(retsym); + retval = retsym->expand_variable(); MY_TEST(retval != 0, "expand undefined set variable"); - free_symtabs(); + symtab::free_symtab(); return rc; } @@ -743,20 +116,20 @@ int test_expand_set_var_during_dump(void) { int rc = 0; int retval; - struct symtab *retsym; + variable *retsym; /* test expanding an defined var within a set variable during var dump*/ - retval = new_set_var("set_var_1", "set_value@{set_var_2}"); + retval = symtab::add_var("set_var_1", "set_value@{set_var_2}"); MY_TEST(retval == 0, "new dump expansion set variable 1"); - retval = new_set_var("set_var_2", "some other set_value"); + retval = symtab::add_var("set_var_2", "some other set_value"); MY_TEST(retval == 0, "new dump expansion set variable 2"); - retsym = lookup_existing_symbol("set_var_1"); + retsym = symtab::lookup_existing_symbol("set_var_1"); MY_TEST(retsym != NULL, "get dump expansion set variable 1"); - __dump_symtab_entry(retsym, 0); - __dump_symtab_entry(retsym, 1); - __dump_symtab_entry(retsym, 0); + retsym->dump(false); + retsym->dump(true); + retsym->dump(false); - free_symtabs(); + symtab::free_symtab(); return rc; } @@ -765,13 +138,17 @@ int test_delete_set_var(void) { int rc = 0; int retval; + variable *deleted; + variable *retsym; - retval = new_set_var("deleteme", "delete this variable"); + retval = symtab::add_var("deleteme", "delete this variable"); MY_TEST(retval == 0, "new delete set variable"); - retval = delete_set_var("deleteme"); - MY_TEST(retval == 0, "delete set variable"); + deleted = symtab::delete_var("deleteme"); + MY_TEST(deleted != NULL, "delete set variable"); + retsym = symtab::lookup_existing_symbol(deleted->var_name.c_str()); + MY_TEST(retsym == NULL, "deleteme was deleted from symtable"); - free_symtabs(); + symtab::free_symtab(); return rc; } @@ -780,13 +157,12 @@ int main(void) { int rc = 0; int retval; - struct set_value *retptr; + struct value_list *list; + struct value_list *val; + variable *retsym; - rc = test_compare_symtab(); - - retval = test_seenlist(); - if (rc == 0) - rc = retval; + val = new_value_list(strdup("a set value")); + retval = symtab::add_set_value("@not_a_set_variable", val); retval = test_add_set_to_boolean(); if (rc == 0) @@ -812,76 +188,94 @@ int main(void) if (rc == 0) rc = retval; - retval = new_set_var("test", "test value"); + retval = symtab::add_var("test", "test value"); MY_TEST(retval == 0, "new set variable 1"); - retval = new_set_var("test", "different value"); + retval = symtab::add_var("test", "different value"); MY_TEST(retval != 0, "new set variable 2"); - retval = new_set_var("testing", "testing"); + retval = symtab::add_var("testing", "testing"); MY_TEST(retval == 0, "new set variable 3"); - retval = new_set_var("monopuff", "Mockingbird"); + retval = symtab::add_var("monopuff", "Mockingbird"); MY_TEST(retval == 0, "new set variable 4"); - retval = new_set_var("stereopuff", "Unsupervised"); + retval = symtab::add_var("stereopuff", "Unsupervised"); MY_TEST(retval == 0, "new set variable 5"); - retval = add_set_value("stereopuff", "Fun to Steal"); + val = new_value_list(strdup("Fun to Steal")); + list = val; + retval = symtab::add_set_value("@stereopuff", val); MY_TEST(retval == 0, "add set value 1"); - retval = add_set_value("stereopuff", "/in/direction"); + val = new_value_list(strdup("/in/direction")); + list_append(list, val); + retval = symtab::add_set_value("@stereopuff", val); MY_TEST(retval == 0, "add set value 2"); - retval = add_set_value("no_such_variable", "stereopuff"); + val = new_value_list(strdup("stereopuff")); + list_append(list, val); + retval = symtab::add_set_value("@no_such_variable", val); MY_TEST(retval != 0, "add to non-existent set var"); - retval = add_boolean_var("abuse", 0); + retval = symtab::add_var("@abuse", 0); MY_TEST(retval == 0, "new boolean variable 1"); - retval = add_boolean_var("abuse", 1); + retval = symtab::add_var("@abuse", 1); MY_TEST(retval != 0, "duplicate boolean variable 1"); - retval = add_boolean_var("stereopuff", 1); + retval = symtab::add_var("@stereopuff", 1); MY_TEST(retval != 0, "duplicate boolean variable 2"); - retval = add_boolean_var("shenanigan", 1); + retval = symtab::add_var("@shenanigan", 1); MY_TEST(retval == 0, "new boolean variable 2"); - retval = get_boolean_var("shenanigan"); - MY_TEST(retval == 1, "get boolean variable 1"); + retsym = symtab::get_boolean_var("@shenanigan"); + MY_TEST(retsym != NULL, "boolean variable 1 exists"); + MY_TEST(retsym->boolean == 1, "get boolean variable 1"); - retval = get_boolean_var("abuse"); - MY_TEST(retval == 0, "get boolean variable 2"); + retsym = symtab::get_boolean_var("@abuse"); + MY_TEST(retsym != NULL, "boolean variable 2 exists"); + MY_TEST(retsym->boolean == 0, "get boolean variable 2"); - retval = get_boolean_var("non_existant"); - MY_TEST(retval < 0, "get nonexistent boolean variable"); + retsym = symtab::get_boolean_var("@non_existant"); + MY_TEST(retsym == NULL, "get nonexistent boolean variable"); - retval = get_boolean_var("stereopuff"); - MY_TEST(retval < 0, "get boolean variable that's declared a set var"); + retsym = symtab::get_boolean_var("@stereopuff"); + MY_TEST(retsym == NULL, "get boolean variable that's declared a set var"); - retptr = get_set_var("daves_not_here_man"); - MY_TEST(retptr == NULL, "get nonexistent set variable"); + retsym = symtab::get_set_var("@daves_not_here_man"); + MY_TEST(retsym == NULL, "get nonexistent set variable"); - retptr = get_set_var("abuse"); - MY_TEST(retptr == NULL, "get set variable that's declared a boolean"); + retsym = symtab::get_set_var("@abuse"); + MY_TEST(retsym == NULL, "get set variable that's declared a boolean"); /* test walking set values */ - retptr = get_set_var("monopuff"); - MY_TEST(retptr != NULL, "get set variable 1"); - retval = strcmp(get_next_set_value(&retptr), "Mockingbird"); - MY_TEST(retval == 0, "get set value 1"); - MY_TEST(get_next_set_value(&retptr) == NULL, "get no more set values 1"); + retsym = symtab::get_set_var("@monopuff"); + MY_TEST(retsym != NULL, "get set variable 1"); + MY_TEST(retsym->values.size() == 1, "only one value"); + MY_TEST(retsym->expanded.size() == 1, "only one expanded"); - retval = new_set_var("eek", "Mocking@{monopuff}bir@{stereopuff}d@{stereopuff}"); + for (std::string value : retsym->values) { + retval = strcmp(value.c_str(), "Mockingbird"); + MY_TEST(retval == 0, "get set value 1"); + } + + for (std::string value : retsym->expanded) { + retval = strcmp(value.c_str(), "Mockingbird"); + MY_TEST(retval == 0, "get set value 1 expanded"); + } + + retval = symtab::add_var("eek", "Mocking@{monopuff}bir@{stereopuff}d@{stereopuff}"); MY_TEST(retval == 0, "new set variable 4"); - dump_symtab(); - expand_variables(); - dump_symtab(); - dump_expanded_symtab(); + symtab::dump(false); + symtab::expand_variables(); + symtab::dump(false); + symtab::dump(true); - free_symtabs(); + free_value_list(list); + symtab::free_symtab(); return rc; } diff --git a/parser/parser_variable.c b/parser/parser_variable.c index 4e42690eb..af3bd7791 100644 --- a/parser/parser_variable.c +++ b/parser/parser_variable.c @@ -31,213 +31,12 @@ #include "profile.h" #include "mount.h" #include "dbus.h" - -static inline const char *get_var_end(const char *var) -{ - const char *eptr = var; - - while (*eptr) { - if (*eptr == '}') - return eptr; - /* first character must be alpha */ - if (eptr == var) { - if (!isalpha(*eptr)) - return NULL; /* invalid char */ - } else { - if (!(*eptr == '_' || isalnum(*eptr))) - return NULL; /* invalid char */ - } - eptr++; - } - return NULL; /* no terminating '}' */ -} - -static struct var_string *split_string(const char *string, const char *var_begin, - const char *var_end) -{ - struct var_string *n = (struct var_string *) calloc(1, sizeof(struct var_string)); - unsigned int offset = strlen("@{"); - if (!n) { - PERROR("Memory allocation error\n"); - return NULL; - } - - if (var_begin != string) { - n->prefix = strndup(string, var_begin - string); - } - - n->var = strndup(var_begin + offset, var_end - (var_begin + offset)); - - if (strlen(var_end + 1) != 0) { - n->suffix = strdup(var_end + 1); - } - - return n; -} - -struct var_string *split_out_var(const char *string) -{ - struct var_string *n = NULL; - const char *sptr; - bool bEscape = false; /* flag to indicate escape */ - - if (!string) /* shouldn't happen */ - return NULL; - - sptr = string; - - while (!n && *sptr) { - switch (*sptr) { - case '\\': - bEscape = !bEscape; - break; - case '@': - if (bEscape) { - bEscape = false; - } else if (*(sptr + 1) == '{') { - const char *eptr = get_var_end(sptr + 2); - if (!eptr) - break; /* no variable end found */ - if (eptr == sptr + 2) { - /* XXX - better diagnostics here, please */ - PERROR("Empty variable name found!\n"); - exit(1); - } - n = split_string(string, sptr, eptr); - } - break; - default: - bEscape = false; - } - sptr++; - } - - return n; -} - -void free_var_string(struct var_string *var) -{ - if (!var) - return; - if (var->prefix) - free(var->prefix); - if (var->var) - free(var->var); - if (var->suffix) - free(var->suffix); - free(var); -} - -static void trim_trailing_slash(std::string& str) -{ - std::size_t found = str.find_last_not_of('/'); - if (found != std::string::npos) - str.erase(found + 1); - else - str.clear(); // str is all '/' -} - -static void write_replacement(const char separator, const char* value, - std::string& replacement, bool filter_leading_slash, - bool filter_trailing_slash) -{ - const char *p = value; - - replacement.append(1, separator); - - if (filter_leading_slash) - while (*p == '/') - p++; - - replacement.append(p); - if (filter_trailing_slash) - trim_trailing_slash(replacement); -} - -static int expand_by_alternations(struct set_value **valuelist, - struct var_string *split_var, - char **name) -{ - char *value, *first_value; - std::string replacement; - bool filter_leading_slash = false; - bool filter_trailing_slash = false; - - first_value = get_next_set_value(valuelist); - if (!first_value) { - PERROR("ASSERT: set variable (%s) should always have at least one value assigned to it\n", - split_var->var); - exit(1); - } - - free(*name); - - value = get_next_set_value(valuelist); - if (!value) { - /* only one entry for the variable, so just sub it in */ - if (asprintf(name, "%s%s%s", - split_var->prefix ? split_var->prefix : "", - first_value, - split_var->suffix ? split_var->suffix : "") == -1) - return -1; - return 0; - } - - if (split_var->prefix && split_var->prefix[strlen(split_var->prefix) - 1] == '/') - filter_leading_slash = true; - if (split_var->suffix && *split_var->suffix == '/') - filter_trailing_slash = true; - - write_replacement('{', first_value, replacement, filter_leading_slash, filter_trailing_slash); - write_replacement(',', value, replacement, filter_leading_slash, filter_trailing_slash); - - while ((value = get_next_set_value(valuelist))) { - write_replacement(',', value, replacement, filter_leading_slash, filter_trailing_slash); - } - - if (asprintf(name, "%s%s}%s", - split_var->prefix ? split_var->prefix : "", - replacement.c_str(), - split_var->suffix ? split_var->suffix : "") == -1) { - return -1; - } - - return 0; -} +#include "symtab.h" /* doesn't handle variables in options atm */ int expand_entry_variables(char **name) { - struct set_value *valuelist; - struct var_string *split_var; - int ret; - - assert(name); - - if (!*name) /* can happen when entry is optional */ - return 0; - - while ((split_var = split_out_var(*name))) { - valuelist = get_set_var(split_var->var); - if (!valuelist) { - int boolean = get_boolean_var(split_var->var); - if (boolean == -1) - PERROR("Found reference to variable %s, but is never declared\n", - split_var->var); - else - PERROR("Found reference to set variable %s, but declared boolean\n", - split_var->var); - exit(1); - } - - ret = expand_by_alternations(&valuelist, split_var, name); - - free_var_string(split_var); - if (ret != 0) - return -1; - - } - return 0; + return variable::expand_by_alternation(name); } static int process_variables_in_entries(struct cod_entry *entry_list) @@ -338,9 +137,9 @@ static std::string escape_re(std::string str) int process_profile_variables(Profile *prof) { - int error = 0, rc; - struct symtab *saved_exec_path = NULL; - struct symtab *saved_attach_path = NULL; + int error = 0; + variable *saved_exec_path = NULL; + variable *saved_attach_path = NULL; /* needs to be before PROFILE_NAME_VARIABLE so that variable will * have the correct name @@ -353,7 +152,7 @@ int process_profile_variables(Profile *prof) /* escape profile name elements that could be interpreted as * regular expressions. */ - error = new_set_var(PROFILE_NAME_VARIABLE, escape_re(prof->get_name(false)).c_str()); + error = symtab::add_var(PROFILE_NAME_VARIABLE, escape_re(prof->get_name(false)).c_str()); if (error) goto out; @@ -364,13 +163,13 @@ int process_profile_variables(Profile *prof) * the attachment. */ /* need to take into account alias, but not yet */ - saved_attach_path = remove_set_var(PROFILE_ATTACH_VAR); - error = new_set_var(PROFILE_ATTACH_VAR, prof->attachment); + saved_attach_path = symtab::delete_var(PROFILE_ATTACH_VAR); + error = symtab::add_var(PROFILE_ATTACH_VAR, (const char*) prof->attachment); if (error) goto cleanup_name; /* update to use kernel vars if available */ - saved_exec_path = remove_set_var(PROFILE_EXEC_VAR); - error = new_set_var(PROFILE_EXEC_VAR, prof->attachment); + saved_exec_path = symtab::delete_var(PROFILE_EXEC_VAR); + error = symtab::add_var(PROFILE_EXEC_VAR, (const char*) prof->attachment); if (error) goto cleanup_attach; } @@ -386,24 +185,18 @@ cleanup: * don't support that yet. */ if (prof->attachment) { - rc = delete_set_var(PROFILE_EXEC_VAR); - if (!error) - error = rc; + symtab::delete_var(PROFILE_EXEC_VAR); if (saved_exec_path) - insert_set_var(saved_exec_path); + symtab::add_var(*saved_exec_path); } cleanup_attach: if (prof->attachment) { - rc = delete_set_var(PROFILE_ATTACH_VAR); - if (!error) - error = rc; + symtab::delete_var(PROFILE_ATTACH_VAR); if (saved_attach_path) - insert_set_var(saved_attach_path); + symtab::add_var(*saved_attach_path); } cleanup_name: - rc = delete_set_var(PROFILE_NAME_VARIABLE); - if (!error) - error = rc; + symtab::delete_var(PROFILE_NAME_VARIABLE); out: return error; @@ -413,82 +206,75 @@ out: #include "unit_test.h" -int test_get_var_end(void) -{ - int rc = 0; - const char *retchar; - const char *testchar; - - testchar = "TRUE}"; - retchar = get_var_end(testchar); - MY_TEST(retchar - testchar == strlen("TRUE"), "get var end for TRUE}"); - - testchar = "some_var}some other text"; - retchar = get_var_end(testchar); - MY_TEST(retchar - testchar == strlen("some_var"), "get var end for some_var}"); - - testchar = "some_var}some other} text"; - retchar = get_var_end(testchar); - MY_TEST(retchar - testchar == strlen("some_var"), "get var end for some_var} 2"); - - testchar = "FALSE"; - retchar = get_var_end(testchar); - MY_TEST(retchar == NULL, "get var end for FALSE"); - - testchar = "pah,pah}pah "; - retchar = get_var_end(testchar); - MY_TEST(retchar == NULL, "get var end for pah,pah}"); - - return rc; -} - int test_split_string(void) { int rc = 0; - char *tst_string, *var_start, *var_end; - struct var_string *ret_struct; + char *tst_string; const char *prefix = "abcdefg"; const char *var = "boogie"; const char *suffix = "suffixication"; + std::tuple result; + std::string result_prefix; + std::string result_var; + std::string result_suffix; + char *pvar; asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix); - var_start = tst_string + strlen(prefix); - var_end = var_start + strlen(var) + strlen("@\{"); - ret_struct = split_string(tst_string, var_start, var_end); - MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split string 1 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 1 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split string 1 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split string 1 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "split string 1 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split string 1 suffix"); + free(pvar); free(tst_string); asprintf(&tst_string, "@{%s}%s", var, suffix); - var_start = tst_string; - var_end = var_start + strlen(var) + strlen("@\{"); - ret_struct = split_string(tst_string, var_start, var_end); - MY_TEST(ret_struct->prefix == NULL, "split string 2 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 2 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split string 2 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(result_prefix.empty(), "split string 2 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "split string 2 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split string 2 suffix"); + free(pvar); free(tst_string); asprintf(&tst_string, "%s@{%s}", prefix, var); - var_start = tst_string + strlen(prefix); - var_end = var_start + strlen(var) + strlen("@\{"); - ret_struct = split_string(tst_string, var_start, var_end); - MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split string 3 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 3 var"); - MY_TEST(ret_struct->suffix == NULL, "split string 3 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split string 3 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "split string 3 var"); + MY_TEST(result_suffix.empty(), "split string 3 suffix"); + free(pvar); free(tst_string); asprintf(&tst_string, "@{%s}", var); - var_start = tst_string; - var_end = var_start + strlen(var) + strlen("@\{"); - ret_struct = split_string(tst_string, var_start, var_end); - MY_TEST(ret_struct->prefix == NULL, "split string 4 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split string 4 var"); - MY_TEST(ret_struct->suffix == NULL, "split string 4 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(result_prefix.empty(), "split string 4 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "split string 4 var"); + MY_TEST(result_suffix.empty(), "split string 4 suffix"); + free(pvar); + free(tst_string); + + asprintf(&tst_string, "%s%s%s", prefix, var, suffix);; + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + MY_TEST(result_prefix.empty(), "split string 5 prefix"); + MY_TEST(result_var.empty(), "split string 5 var"); + MY_TEST(result_suffix.empty(), "split string 5 suffix"); free(tst_string); return rc; @@ -498,135 +284,228 @@ int test_split_out_var(void) { int rc = 0; char *tst_string, *tmp; - struct var_string *ret_struct; const char *prefix = "abcdefg"; const char *var = "boogie"; const char *var2 = "V4rW1thNum5"; const char *var3 = "boogie_board"; const char *suffix = "suffixication"; + std::tuple result; + std::string result_prefix; + std::string result_var; + std::string result_suffix; + char *pvar = NULL; /* simple case */ asprintf(&tst_string, "%s@{%s}%s", prefix, var, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 1 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 1 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 1 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable 1 pvar"); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 1 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 1 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 1 suffix"); + if (pvar) + free(pvar); free(tst_string); /* no prefix */ asprintf(&tst_string, "@{%s}%s", var, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct->prefix == NULL, "split out var 2 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 2 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 2 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable 2 pvar"); + MY_TEST(result_prefix.empty(), "extract_variable 2 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 2 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 2 suffix"); + if (pvar) + free(pvar); free(tst_string); /* no suffix */ asprintf(&tst_string, "%s@{%s}", prefix, var); - ret_struct = split_out_var(tst_string); - MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 3 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 3 var"); - MY_TEST(ret_struct->suffix == NULL, "split out var 3 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable 3 pvar"); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 3 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 3 var"); + MY_TEST(result_suffix.empty(), "extract_variable 3 suffix"); + if (pvar) + free(pvar); free(tst_string); /* var only */ asprintf(&tst_string, "@{%s}", var); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct->prefix == NULL, "split out var 4 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 4 var"); - MY_TEST(ret_struct->suffix == NULL, "split out var 4 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable 4 pvar"); + MY_TEST(result_prefix.empty(), "extract_variable 4 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 4 var"); + MY_TEST(result_suffix.empty(), "extract_variable 4 suffix"); + if (pvar) + free(pvar); free(tst_string); /* quoted var, shouldn't split */ asprintf(&tst_string, "%s\\@{%s}%s", prefix, var, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct == NULL, "split out var - quoted @"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + MY_TEST(result_prefix.empty(), "extract_variable - quoted @ prefix"); + MY_TEST(result_var.empty(), "extract_variable - quoted @ var"); + MY_TEST(result_suffix.empty(), "extract_variable - quoted @ suffix"); free(tst_string); /* quoted \, split should succeed */ asprintf(&tst_string, "%s\\\\@{%s}%s", prefix, var, suffix); - ret_struct = split_out_var(tst_string); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); tmp = strndup(tst_string, strlen(prefix) + 2); - MY_TEST(strcmp(ret_struct->prefix, tmp) == 0, "split out var 5 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 5 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 5 suffix"); - free_var_string(ret_struct); + MY_TEST(pvar != NULL, "extract_variable 5 pvar"); + MY_TEST(strcmp(result_prefix.c_str(), tmp) == 0, "extract_variable 5 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 5 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 5 suffix"); + if (pvar) + free(pvar); free(tst_string); free(tmp); /* un terminated var, should fail */ asprintf(&tst_string, "%s@{%s%s", prefix, var, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct == NULL, "split out var - un-terminated var"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + MY_TEST(result_prefix.empty(), "extract_variable - un-terminated var prefix"); + MY_TEST(result_var.empty(), "extract_variable - un-terminated var var"); + MY_TEST(result_suffix.empty(), "extract_variable - un-terminated var suffix"); free(tst_string); /* invalid char in var, should fail */ asprintf(&tst_string, "%s@{%s^%s}%s", prefix, var, var, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct == NULL, "split out var - invalid char in var"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar == NULL, "process_var - invalid char in var"); + if (pvar) + free(pvar); free(tst_string); /* two vars, should only strip out first */ asprintf(&tmp, "@{%s}%s}", suffix, suffix); asprintf(&tst_string, "%s@{%s}%s", prefix, var, tmp); - ret_struct = split_out_var(tst_string); - MY_TEST(strcmp(ret_struct->prefix, prefix) == 0, "split out var 6 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 6 var"); - MY_TEST(strcmp(ret_struct->suffix, tmp) == 0, "split out var 6 suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable 6 pvar"); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "extract_variable 6 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 6 var"); + MY_TEST(strcmp(result_suffix.c_str(), tmp) == 0, "extract_variable 6 suffix"); + if (pvar) + free(pvar); free(tst_string); free(tmp); /* quoted @ followed by var, split should succeed */ asprintf(&tst_string, "%s\\@@{%s}%s", prefix, var, suffix); - ret_struct = split_out_var(tst_string); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); tmp = strndup(tst_string, strlen(prefix) + 2); - MY_TEST(strcmp(ret_struct->prefix, tmp) == 0, "split out var 7 prefix"); - MY_TEST(strcmp(ret_struct->var, var) == 0, "split out var 7 var"); - MY_TEST(strcmp(ret_struct->suffix, suffix) == 0, "split out var 7 suffix"); - free_var_string(ret_struct); + MY_TEST(pvar != NULL, "extract_variable 7 pvar"); + MY_TEST(strcmp(result_prefix.c_str(), tmp) == 0, "extract_variable 7 prefix"); + MY_TEST(strcmp(pvar, var) == 0, "extract_variable 7 var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "extract_variable 7 suffix"); + if (pvar) + free(pvar); free(tst_string); free(tmp); /* numeric char in var, should succeed */ asprintf(&tst_string, "%s@{%s}%s", prefix, var2, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out numeric var prefix"); - MY_TEST(ret_struct && strcmp(ret_struct->var, var2) == 0, "split numeric var var"); - MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out numeric var suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable numeric var pvar"); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split out numeric var prefix"); + MY_TEST(strcmp(pvar, var2) == 0, "split numeric var var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split out numeric var suffix"); + if (pvar) + free(pvar); free(tst_string); /* numeric first char in var, should fail */ asprintf(&tst_string, "%s@{6%s}%s", prefix, var2, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct == NULL, "split out var - numeric first char in var"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar == NULL, "process_var - invalid char in var"); + if (pvar) + free(pvar); free(tst_string); /* underscore char in var, should succeed */ asprintf(&tst_string, "%s@{%s}%s", prefix, var3, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct && strcmp(ret_struct->prefix, prefix) == 0, "split out underscore var prefix"); - MY_TEST(ret_struct && strcmp(ret_struct->var, var3) == 0, "split out underscore var"); - MY_TEST(ret_struct && strcmp(ret_struct->suffix, suffix) == 0, "split out underscore var suffix"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar != NULL, "extract_variable underscore var pvar"); + MY_TEST(strcmp(result_prefix.c_str(), prefix) == 0, "split out underscore var prefix"); + MY_TEST(strcmp(pvar, var3) == 0, "split out underscore var"); + MY_TEST(strcmp(result_suffix.c_str(), suffix) == 0, "split out underscore var suffix"); + if (pvar) + free(pvar); free(tst_string); /* underscore first char in var, should fail */ asprintf(&tst_string, "%s@{_%s%s}%s", prefix, var2, var3, suffix); - ret_struct = split_out_var(tst_string); - MY_TEST(ret_struct == NULL, "split out var - underscore first char in var"); - free_var_string(ret_struct); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar == NULL, "process_var - invalid char in var"); + if (pvar) + free(pvar); free(tst_string); + /* empty var name, should fail */ + asprintf(&tst_string, "%s@{}%s", prefix, suffix); + result = extract_variable(tst_string); + result_prefix = std::get<0>(result); + result_var = std::get<1>(result); + result_suffix = std::get<2>(result); + pvar = variable::process_var(result_var.c_str()); + MY_TEST(pvar == NULL, "process_var - empty var name"); + if (pvar) + free(pvar); + free(tst_string); + return rc; } int main(void) @@ -634,10 +513,6 @@ int main(void) int rc = 0; int retval; - retval = test_get_var_end(); - if (retval != 0) - rc = retval; - retval = test_split_string(); if (retval != 0) rc = retval; diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index 21ff2d783..0b5d4111f 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -193,6 +193,7 @@ static void abi_features(char *filename, bool search); #include "network.h" #include "all_rule.h" #include "cond_expr.h" + #include "symtab.h" } %union { @@ -492,71 +493,37 @@ alias: TOK_ALIAS TOK_ID TOK_ARROW TOK_ID TOK_END_OF_RULE varassign: TOK_SET_VAR TOK_EQUALS valuelist { - struct value_list *list = $3; - char *var_name = process_var($1); int err; - if (!list || !list->value) - yyerror("Assert: valuelist returned NULL"); - PDEBUG("Matched: set assignment for (%s)\n", $1); - err = new_set_var(var_name, list->value); + err = symtab::add_var($1, $3); if (err) { - free(var_name); yyerror("variable %s was previously declared", $1); /* FIXME: it'd be handy to report the previous location */ } - for (list = list->next; list; list = list->next) { - err = add_set_value(var_name, list->value); - if (err) { - free(var_name); - yyerror("Error adding %s to set var %s", - list->value, $1); - } - } + free_value_list($3); - free(var_name); free($1); } varassign: TOK_SET_VAR TOK_ADD_ASSIGN valuelist { - struct value_list *list = $3; - char *var_name = process_var($1); int err; - if (!list || !list->value) - yyerror("Assert: valuelist returned NULL"); - PDEBUG("Matched: additive assignment for (%s)\n", $1); - /* do the first one outside the loop, subsequent - * failures are indicative of symtab failures */ - err = add_set_value(var_name, list->value); + err = symtab::add_set_value($1, $3); if (err) { - free(var_name); yyerror("variable %s was not previously declared, but is being assigned additional values", $1); } - for (list = list->next; list; list = list->next) { - err = add_set_value(var_name, list->value); - if (err) { - free(var_name); - yyerror("Error adding %s to set var %s", - list->value, $1); - } - } free_value_list($3); - free(var_name); free($1); } varassign: TOK_BOOL_VAR TOK_EQUALS TOK_VALUE { int boolean, err; - char *var_name = process_var($1); - PDEBUG("Matched: boolean assignment (%s) to %s\n", $1, $3); boolean = str_to_boolean($3); if (boolean == -1) { yyerror("Invalid boolean assignment for (%s): %s is not true or false", $1, $3); } - err = add_boolean_var(var_name, boolean); - free(var_name); + err = symtab::add_var($1, boolean); if (err) { yyerror("variable %s was previously declared", $1); /* FIXME: it'd be handy to report the previous location */ diff --git a/parser/symtab.cc b/parser/symtab.cc new file mode 100644 index 000000000..12e89e240 --- /dev/null +++ b/parser/symtab.cc @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2025 + * Canonical Ltd. (All rights reserved) + * + * 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. + */ + +#include "symtab.h" + +template +int symtab::add_var(const char *var, T value) +{ + char *var_name = variable::process_var(var); + if (!var_name) + return 1; + + const auto search = my_symtab.find(var_name); + if (search != my_symtab.end()) { + /* already existing variable */ + PERROR("'%s' is already defined\n", var); + free(var_name); + return 1; + } + my_symtab.emplace(var_name, variable(var_name, value)); + free(var_name); + return 0; +} + +int symtab::add_var(const char *var, int value) +{ + return add_var(var, value); +} + +int symtab::add_var(const char *var, struct value_list *value) +{ + return add_var(var, value); +} + +int symtab::add_var(const char *var_name, const char *value) +{ + /* note that here var_name must be processed already */ + const auto search = my_symtab.find(var_name); + if (search != my_symtab.end()) { + /* already existing variable */ + PERROR("'%s' is already defined\n", var_name); + return 1; + } + my_symtab.emplace(var_name, variable(var_name, value)); + return 0; +} + + +int symtab::add_var(variable var) +{ + const auto search = my_symtab.find(var.var_name); + if (search != my_symtab.end()) { + /* already existing variable */ + PERROR("'%s' is already defined\n", var.var_name.c_str()); + return 1; + } + my_symtab.emplace(var.var_name, var); + return 0; +} + +variable *symtab::lookup_existing_symbol(const char *var_name) +{ + if (!var_name) + return nullptr; + + const auto var = my_symtab.find(var_name); + if (var == my_symtab.end()) { + return nullptr; + } + return &(var->second); +} + +int symtab::add_set_value(const char *var_name, struct value_list *value) +{ + char *pvar_name = variable::process_var(var_name); + variable *var = lookup_existing_symbol(pvar_name); + if (!var) { + PERROR("Failed to find declaration for: %s\n", pvar_name); + free(pvar_name); + return 1; + } + free(pvar_name); + return var->add_set_value(value); +} + +variable *symtab::delete_var(const char *var_name) +{ + variable *var = lookup_existing_symbol(var_name); + if (!var) { + return var; + } + if (var->type != sd_set) { + PERROR("ASSERT: delete_set_var: deleting %s but is a boolean variable\n", + var_name); + exit(1); + } + variable *save = new variable(*var); + my_symtab.erase(var->var_name); + return save; +} + +void symtab::free_symtab() +{ + my_symtab.erase(my_symtab.begin(), my_symtab.end()); +} + +void symtab::dump(bool do_expanded) +{ + for (auto var : my_symtab) { + var.second.dump(do_expanded); + } +} + +void symtab::expand_variables() +{ + for (auto var : my_symtab) { + if (var.second.type != sd_boolean) + var.second.expand_variable(); + } +} + + +variable *symtab::get_set_var(const char *name) +{ + char *var_name = variable::process_var(name); + variable *var = lookup_existing_symbol(var_name); + if (!var) { + return var; + } + if (var->type != sd_set) { + PERROR("Variable %s is not a set variable\n", var_name); + return nullptr; + } + var->expand_variable(); + return var; +} + +variable *symtab::get_boolean_var(const char *name) +{ + char *var_name = variable::process_var(name); + variable *var = lookup_existing_symbol(var_name); + if (!var) { + return var; + } + if (var->type != sd_boolean) { + PERROR("Variable %s is not a boolean variable\n", var_name); + return nullptr; + } + free(var_name); + return var; +} + +std::unordered_map symtab::my_symtab; diff --git a/parser/symtab.h b/parser/symtab.h new file mode 100644 index 000000000..3ab3e362d --- /dev/null +++ b/parser/symtab.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 + * Canonical Ltd. (All rights reserved) + * + * 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. + */ + +#ifndef __AA_SYMTAB_H +#define __AA_SYMTAB_H + +#include +#include + +#include "variable.h" +#include "parser.h" + +class symtab { +private: + static std::unordered_map my_symtab; +public: + template + static int add_var(const char *var, T value); + static int add_var(const char *var, int value); + static int add_var(const char *var, struct value_list *value); + static int add_var(const char *var, const char *value); + static int add_var(variable var); + static int add_set_value(const char *var, struct value_list *value); + static void dump(bool do_expanded); + static void free_symtab(void); + static void expand_variables(void); + static variable *lookup_existing_symbol(const char *var_name); + static variable *get_set_var(const char *var_name); + static variable *get_boolean_var(const char *var_name); + static variable *delete_var(const char *var_name); +}; + +#endif /* __AA_SYMTAB_H */ diff --git a/parser/variable.cc b/parser/variable.cc new file mode 100644 index 000000000..c43062e7a --- /dev/null +++ b/parser/variable.cc @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2025 + * Canonical Ltd. (All rights reserved) + * + * 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. + */ + +#include +#include +#include + +#include "variable.h" +#include "symtab.h" + +variable::variable(const char *var_name, struct value_list *values): + type(sd_set), + var_name(var_name) +{ + struct value_list *entry = NULL; + if (!values || !values->value) { + yyerror("Assert: valuelist returned NULL"); + } + PDEBUG("Matched: set assignment for (%s)\n", var_name); + + list_for_each(values, entry) { + this->values.insert(entry->value); + } +} + +variable::variable(const char *var_name, const char *value): + type(sd_set), + var_name(var_name) +{ + PDEBUG("Matched: set assignment for (%s)\n", var_name); + this->values.insert(value); +} + +variable::variable(const char *var_name, int boolean): + type(sd_boolean), + var_name(var_name), + boolean(boolean) +{ + PDEBUG("Matched: boolean assignment (%s) to %d\n", var_name, boolean); +} + +char *variable::process_var(const char *var) +{ + const char *orig = var; + const char *valid; + int len = strlen(var); + int i; + + if (*orig == '@' || *orig == '$') { + orig++; + len--; + } else { + PERROR("ASSERT: Found var '%s' without variable prefix\n", + var); + return NULL; + } + + if (*orig == '{') { + orig++; + len--; + if (orig[len - 1] != '}') { + PERROR("ASSERT: No matching '}' in variable '%s'\n", + var); + return NULL; + } else + len--; + } + + valid = orig; + for (i = 0; i < len; i++) { + /* first character must be alpha */ + if (valid[i] == *orig) { + if (!isalpha(valid[i])) { + PERROR("Variable '%s' must start with alphabet letters\n", + var); + return NULL; + } + } else if (!(valid[i] == '_' || isalnum(valid[i]))) { + PERROR("Variable '%s' contains invalid characters\n", + var); + return NULL; + } + } + + return strndup(orig, len); +} + +int variable::add_set_value(struct value_list *values) +{ + struct value_list *entry = NULL; + PDEBUG("Matched: additive assignment for (%s)\n", var_name.c_str()); + + if (type != sd_set) { + PERROR("Variable %s is not a set variable\n", var_name.c_str()); + return 2; + } + + list_for_each(values, entry) { + this->values.insert(entry->value); + } + return 0; +} + +std::tuple extract_variable(const std::string& input) +{ + std::string prefix = ""; + std::string variable = ""; + std::string suffix = ""; + + ssize_t start_pattern_pos = -1; + ssize_t end_pattern_pos = -1; + size_t var_len; + + bool bEscape = false; + size_t i; + + for (i = 0; i < input.size(); i++) { + switch(input[i]) { + case '\\': + bEscape = !bEscape; + break; + case '@': + if (bEscape) { + bEscape = false; + } else if (input[i + 1] == '{') { + start_pattern_pos = i; + i += 2; + } + break; + case '}': + if (bEscape) { + bEscape = false; + } else if (start_pattern_pos != -1) { + end_pattern_pos = i; + goto found; + } + break; + default: + bEscape = false; + break; + } + } + +found: + if (start_pattern_pos == -1 || end_pattern_pos == -1) { + return std::make_tuple(prefix, variable, suffix); // "@{" or '}' not found + } + + var_len = end_pattern_pos - start_pattern_pos + 1; + + prefix = input.substr(0, start_pattern_pos); + variable = input.substr(start_pattern_pos, var_len); + suffix = input.substr(end_pattern_pos + 1); + + return std::make_tuple(prefix, variable, suffix); +} + +static void trim_leading_slash(std::string& str) +{ + std::size_t found = str.find_first_not_of('/'); + if (found != std::string::npos) + str.erase(0, found); + else + str.clear(); // str is all '/' +} + +static void trim_trailing_slash(std::string& str) +{ + std::size_t found = str.find_last_not_of('/'); + if (found != std::string::npos) + str.erase(found + 1); + else + str.clear(); // str is all '/' +} + +int variable::expand_by_alternation(char **name) +{ + std::string expanded_name = ""; + bool filter_leading_slash = false; + bool filter_trailing_slash = false; + + if (!name) { + PERROR("ASSERT: name to be expanded cannot be NULL\n"); + exit(1); + } + if (!*name) /* can happen when entry is optional */ + return 0; + + auto result = extract_variable(*name); + std::string prefix = std::get<0>(result); + std::string var = std::get<1>(result); + std::string suffix = std::get<2>(result); + + if (prefix.empty() && var.empty() && suffix.empty()) { + return 0; /* no var found, name is unchanged */ + } + + free(*name); + + if (!prefix.empty() && prefix[prefix.size() - 1] == '/') { + /* make sure to not collapse / in the beginning of the path */ + std::size_t found = prefix.find_first_not_of('/'); + if (found != std::string::npos) + filter_leading_slash = true; + } + if (!suffix.empty() && suffix[0] == '/') + filter_trailing_slash = true; + + variable *ref = symtab::get_set_var(var.c_str()); + if (!ref) { + PERROR("Failed to find declaration for: %s\n", var.c_str()); + return 1; + } + + size_t setsize = ref->expanded.size(); + auto i = ref->expanded.begin(); + + if (setsize > 1) { + expanded_name += "{"; + } + + do { + std::string s = *i; + if (filter_leading_slash) + trim_leading_slash(s); + if (filter_trailing_slash) + trim_trailing_slash(s); + + if (i != ref->expanded.begin()) { + expanded_name += ","; + } + + expanded_name += s; + } while (++i != ref->expanded.end()); + + if (setsize > 1) { + expanded_name += "}"; + } + + expanded_name = prefix + expanded_name + suffix; + *name = strdup(expanded_name.c_str()); + if (!*name) { + errno = ENOMEM; + return -1; + } + /* recursive until no variables are found in *name */ + return expand_by_alternation(name); +} + +int variable::expand_variable() +{ + int rc = 0; + + if (type == sd_boolean) { + PERROR("Referenced variable %s is a boolean used in set context\n", + var_name.c_str()); + return 2; + } + + /* already done */ + if (!expanded.empty()) + return 0; + + expanding = true; + + std::list work_set(values.begin(), values.end()); + for (auto value : work_set) { + auto result = extract_variable(value); + std::string prefix = std::get<0>(result); + std::string var = std::get<1>(result); + std::string suffix = std::get<2>(result); + + if (prefix.empty() && var.empty() && suffix.empty()) { + expanded.insert(value); /* no var left to expand */ + continue; + } + char *name = variable::process_var(var.c_str()); + variable *ref = symtab::lookup_existing_symbol(name); + if (!ref) { + PERROR("Failed to find declaration for: %s\n", var.c_str()); + rc = 1; + goto out; + } + if (ref->expanding) { + PERROR("Variable @{%s} is referenced recursively (by @{%s})\n", + ref->var_name.c_str(), var_name.c_str()); + rc = 1; + goto out; + } + rc = ref->expand_variable(); + if (rc != 0) { + goto out; + } + + if (ref->expanded.empty()) { + PERROR("ASSERT: Variable @{%s} should have been expanded but isn't\n", + ref->var_name.c_str()); + exit(1); + } + for (auto refvalue : ref->expanded) { + /* there could still be vars in suffix, so add + * to work_set, not expanded */ + work_set.push_back(prefix + refvalue + suffix); + } + } + +out: + expanding = false; + return rc; +} + +void variable::dump_set_values(std::set values) +{ + for (auto value : values) + printf(" \"%s\"", value.c_str()); +} + +void variable::dump(bool do_expanded) +{ + switch(type) { + case sd_boolean: + printf("$%s = %s\n", var_name.c_str(), + boolean ? "true" : "false"); + break; + case sd_set: + printf("@%s =", var_name.c_str()); + if (do_expanded) { + if (expanded.empty()) { + expand_variable(); + } + dump_set_values(expanded); + } else { + dump_set_values(values); + } + printf("\n"); + break; + default: + PERROR("ASSERT: unknown symbol table type for %s\n", var_name.c_str()); + exit(1); + } +} diff --git a/parser/variable.h b/parser/variable.h new file mode 100644 index 000000000..977194666 --- /dev/null +++ b/parser/variable.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 + * Canonical Ltd. (All rights reserved) + * + * 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. + */ + +#ifndef __AA_VARIABLE_H +#define __AA_VARIABLE_H + +#include +#include +#include "parser.h" + +enum var_type { + sd_boolean, + sd_set, +}; + +class variable { +public: + enum var_type type; + std::string var_name; + int boolean; + std::set values; + std::set expanded; + std::string expanding_by = ""; + bool expanding = false; + + variable(const char *var_name, struct value_list *values); + variable(const char *var_name, const char *value); + variable(const char *var_name, int boolean); + + int add_set_value(struct value_list *value); + int expand_variable(void); + static int expand_by_alternation(char **name); + void dump_set_values(std::set values); + void dump(bool expanded); + + virtual ~variable() {}; + + /* strip off surrounding delimiters around variables */ + static char *process_var(const char *var); +}; + +std::tuple extract_variable(const std::string& input); + +#endif /* __AA_VARIABLE_H */