2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 01:57:43 +00:00

Merge parser: refactor variables and symbols table into their own class

While symtab for now has only static members, it will allow for a
change in the future for each profile to have their own symbols like
profile_name, etc.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1711
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
John Johansen 2025-07-17 23:08:39 +00:00
commit ad16a5c5c0
13 changed files with 994 additions and 1168 deletions

View File

@ -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 $@

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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;
}

View File

@ -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<std::string, std::string, std::string> 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<std::string, std::string, std::string> 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;

View File

@ -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 */

167
parser/symtab.cc Normal file
View File

@ -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 <typename T>
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<int>(var, value);
}
int symtab::add_var(const char *var, struct value_list *value)
{
return add_var<struct value_list *>(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<std::string, variable> symtab::my_symtab;

47
parser/symtab.h Normal file
View File

@ -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 <unordered_map>
#include <string>
#include "variable.h"
#include "parser.h"
class symtab {
private:
static std::unordered_map<std::string, variable> my_symtab;
public:
template <typename T>
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 */

View File

@ -1220,6 +1220,22 @@ run_tests()
"@{BAR}=bin/ \#value
/t { /@{BAR} r, }"
# // at the beginning of the variable contents should not be filtered
verify_binary_equality "// in the beginning of variable content" \
"/t //bin/foo { }" \
"/t //bin//foo { }" \
"@{BAR}=/bin/
/t /@{BAR}/foo { }" \
"@{FOO}=/foo
/t //bin/@{FOO} { }" \
"@{BAR}=/bin/
@{FOO}=/foo
/t /@{BAR}/@{FOO} { }" \
"@{BAR}=/bin/
@{FOO}=/foo
@{ROOT}=/
/t @{ROOT}@{BAR}/@{FOO} { }"
test_parser_variables
# verify combinations of different priority levels

355
parser/variable.cc Normal file
View File

@ -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 <cctype>
#include <list>
#include <tuple>
#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<std::string, std::string, std::string> 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<std::string> 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<std::string> 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);
}
}

58
parser/variable.h Normal file
View File

@ -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 <set>
#include <string>
#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<std::string> values;
std::set<std::string> 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<std::string> values);
void dump(bool expanded);
virtual ~variable() {};
/* strip off surrounding delimiters around variables */
static char *process_var(const char *var);
};
std::tuple<std::string, std::string, std::string> extract_variable(const std::string& input);
#endif /* __AA_VARIABLE_H */