#include <cil/android.h>
#include <sepol/policydb/hashtab.h>
#include <stdlib.h>
#include <string.h>

#include "cil_binary.h"
#include "cil_build_ast.h"
#include "cil_internal.h"
#include "cil_strpool.h"
#include "cil_symtab.h"
#include "cil_tree.h"

#define VER_MAP_SZ (1 << 12)

/* added to hashmap - currently unused as hashmap is used as a set */
struct version_datum {
	struct cil_db *db;
	struct cil_tree_node *ast_node;
	char *orig_name;
};

struct version_args {
	struct cil_db *db;
	hashtab_t vers_map;
	const char *num;
};

struct policydb_amend_args {
	const struct cil_db *db;
	policydb_t *pdb;
	void **type_value_to_cil;
};

enum plat_flavor {
	PLAT_NONE = 0,
	PLAT_TYPE,
	PLAT_ATTRIB
};

static unsigned int ver_map_hash_val(hashtab_t h, const_hashtab_key_t key)
{
	/* from cil_stpool.c */
	char *p, *keyp;
	size_t size;
	unsigned int val;

	val = 0;
	keyp = (char*)key;
	size = strlen(keyp);
	for (p = keyp; ((size_t) (p - keyp)) < size; p++)
		val =
			(val << 4 | (val >> (8 * sizeof(unsigned int) - 4))) ^ (*p);
	return val & (h->size - 1);
}


static int ver_map_key_cmp(hashtab_t h __attribute__ ((unused)),
			   const_hashtab_key_t key1, const_hashtab_key_t key2)
{
	/* hashtab_key_t is just a const char* underneath */
	return strcmp(key1, key2);
}

/*
 * version_datum  pointers all refer to memory owned elsewhere, so just free the
 * datum itself.
 */
static int ver_map_entry_destroy(__attribute__ ((unused))hashtab_key_t k,
				 hashtab_datum_t d, __attribute__ ((unused))void *args)
{
	free(d);
	return 0;
}

static void ver_map_destroy(hashtab_t h)
{
	hashtab_map(h, ver_map_entry_destroy, NULL);
	hashtab_destroy(h);
}

static int __extract_attributees_helper(struct cil_tree_node *node, uint32_t *finished, void *extra_args)
{
	int rc = SEPOL_ERR;
	struct version_args *args = (struct version_args *) extra_args;
	char *key;
	struct version_datum *datum;

	if (node == NULL || finished == NULL || extra_args == NULL) {
		goto exit;
	}

	switch (node->flavor) {
	case CIL_ROLE:
		cil_log(CIL_ERR, "%s unsupported statement in attributee policy (line %d)\n",
			CIL_KEY_ROLE, node->line);
		rc = SEPOL_ERR;
		break;
	case CIL_TYPE:
	case CIL_TYPEATTRIBUTE:
		datum = cil_malloc(sizeof(*datum));
		datum->db = args->db;
		datum->ast_node = node;
		datum->orig_name = DATUM(node->data)->name;
		key = datum->orig_name;
		if (!strncmp(key, "base_typeattr_", 14)) {
			/* checkpolicy creates base attributes which are just typeattributesets,
			   of the existing types and attributes.  These may be differnt in
			   every checkpolicy output, ignore them here, they'll be dealt with
			   as a special case when attributizing. */
			free(datum);
		} else {
			rc = hashtab_insert(args->vers_map, (hashtab_key_t) key, (hashtab_datum_t) datum);
			if (rc != SEPOL_OK) {
				goto exit;
			}
		}
		break;
	case CIL_TYPEALIAS:
		cil_log(CIL_ERR, "%s unsupported statement in attributee policy (line %d)\n",
			CIL_KEY_TYPEALIAS, node->line);
		goto exit;
		break;
	case CIL_TYPEPERMISSIVE:
		cil_log(CIL_ERR, "%s unsupported statement in attributee policy (line %d)\n",
			CIL_KEY_TYPEPERMISSIVE, node->line);
		goto exit;
		break;
	case CIL_NAMETYPETRANSITION:
	case CIL_TYPE_RULE:
		cil_log(CIL_ERR, "%s unsupported statement in attributee policy (line %d)\n",
			CIL_KEY_TYPETRANSITION, node->line);
		goto exit;
		break;
	default:
		break;
	}
	return SEPOL_OK;
exit:
	return rc;
}

/*
 * For the given db, with an already-built AST, fill the vers_map hash table
 * with every encountered type and attribute.  This could eventually be expanded
 * to include other language constructs, such as users and roles, in which case
 * multiple hash tables would be needed.  These tables can then be used by
 * attributize() to change all references to these types.
 */
int cil_extract_attributees(struct cil_db *db, hashtab_t vers_map)
{
	/* walk ast. */
	int rc = SEPOL_ERR;
	struct version_args extra_args;
	extra_args.db = db;
	extra_args.vers_map = vers_map;
	extra_args.num = NULL;
	rc = cil_tree_walk(db->ast->root, __extract_attributees_helper, NULL, NULL, &extra_args);
	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

static enum plat_flavor __cil_get_plat_flavor(hashtab_t vers_map, hashtab_key_t key)
{
	enum plat_flavor rc;
	struct version_datum *vers_datum;

	vers_datum = (struct version_datum *)hashtab_search(vers_map, key);
	if (vers_datum == NULL) {
		return PLAT_NONE;
	}
	switch (vers_datum->ast_node->flavor) {
	case CIL_TYPE:
		rc = PLAT_TYPE;
		break;
	case CIL_TYPEATTRIBUTE:
		rc = PLAT_ATTRIB;
		break;
	default:
		rc = PLAT_NONE;
		break;
	}
	return rc;
}

/*
 * Takes the old name and version string and creates a new strpool entry by
 * combining them.
 */
static char *__cil_attrib_get_versname(char *old, const char *vers)
{
	size_t len = 0;
	char *tmp_new = NULL;
	char *final;

	len += strlen(old) + strlen(vers) + 2;
	tmp_new = cil_malloc(len);
	snprintf(tmp_new, len, "%s_%s", old, vers);
	final = cil_strpool_add(tmp_new);
	free(tmp_new);
	return final;
}

/*
 * Change type to attribute - create new versioned name based on old, create
 * typeattribute node add to the existing type node.
 */
static int __cil_attrib_convert_type(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_type *type = (struct cil_type *)node->data;
	struct cil_typeattribute *typeattr = NULL;
	struct cil_tree_node *new_ast_node = NULL;
	char *new_key;

	cil_typeattribute_init(&typeattr);

	new_key = __cil_attrib_get_versname(type->datum.name, args->num);

	/* create new tree node to contain typeattribute and add to tree */
	cil_tree_node_init(&new_ast_node);
	new_ast_node->parent = node->parent;
	new_ast_node->next = node->next;
	node->next = new_ast_node;

	rc = cil_gen_node(args->db, new_ast_node, (struct cil_symtab_datum *) typeattr,
			  new_key, CIL_SYM_TYPES, CIL_TYPEATTRIBUTE);
	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

/*
 * Update datum - create new key, remove entry under old key,
 * update entry, and insert under new key
 */
static int __cil_attrib_swap_symtab_key(struct cil_tree_node *node, char *old_key,
					const char *num)
{
	int rc = SEPOL_ERR;
	char *new_key;
	symtab_t *symtab;
	struct cil_symtab_datum *datum = (struct cil_symtab_datum *) node->data;

	new_key = __cil_attrib_get_versname(old_key, num);

	symtab = datum->symtab;

	/* TODO: remove, but what happens to other nodes on this datum ?*/
	cil_list_remove(datum->nodes, CIL_NODE, node, 0);
	cil_symtab_remove_datum(datum);

	rc = cil_symtab_insert(symtab, new_key, datum, node);

	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

/*
 * expressions may contains strings which are not in the type-attribute
 * namespace, so this is not a general cil_expr attributizer.
 * TODO: add support for other types of expressions which may contain types.
 */
static int cil_attrib_type_expr(struct cil_list *expr_str, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_list_item *curr = NULL;
	char *new;
	hashtab_key_t key;

	/* iterate through cil_list, replacing types */
	cil_list_for_each(curr, expr_str) {
		switch(curr->flavor) {
		case CIL_LIST:
			rc = cil_attrib_type_expr((struct cil_list *)curr->data, args);
			if (rc != SEPOL_OK)
				goto exit;
			break;
		case CIL_STRING:
			key = (hashtab_key_t) curr->data;
			enum plat_flavor pf = __cil_get_plat_flavor(args->vers_map, key);
			if (!strncmp(curr->data, "base_typeattr_", 14) || pf == PLAT_TYPE) {
				new = __cil_attrib_get_versname((char *) curr->data, args->num);
				curr->data = (void *) new;
			}
			break;
		case CIL_DATUM:
			cil_log(CIL_ERR, "AST already resolved. Not yet supported.\n");
			rc = SEPOL_ERR;
			goto exit;
			break;
		default:
			break;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_check_context(struct cil_context *ctxt, struct version_args *args)
{
	int rc = SEPOL_ERR;
	hashtab_key_t key;

	if (ctxt->type != NULL) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported.\n");
		goto exit;
	}

	key = (hashtab_key_t) ctxt->type_str;
	if (__cil_get_plat_flavor(args->vers_map, key) != PLAT_NONE) {
        /* TODO: reinstate check, but leave out for now
		cil_log(CIL_ERR, "AST contains context with platform public type: %s\n",
			ctxt->type_str);
		rc = SEPOL_ERR;
		goto exit; */
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_sidcontext(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_sidcontext *sidcon = (struct cil_sidcontext *)node->data;

	if (sidcon->context_str == NULL) {
		/* sidcon contains an anon context, which needs to have type checked */
		rc = cil_attrib_check_context(sidcon->context, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_context(struct cil_tree_node *node, struct version_args *args)
{
	struct cil_context *ctxt = (struct cil_context *)node->data;

	return cil_attrib_check_context(ctxt, args);
}

static int cil_attrib_roletype(struct cil_tree_node *node,
			       __attribute__((unused)) struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_roletype *roletype = (struct cil_roletype *)node->data;

	if (roletype->role) {
		cil_log(CIL_ERR, "AST already resolved.  !!! Not yet supported.\n");
		goto exit;
	}
	key = roletype->type_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		roletype->type_str = __cil_attrib_get_versname(key, args->num);
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_type(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_type *type = (struct cil_type *)node->data;
	char *key = type->datum.name;

	if (type->value) {
		cil_log(CIL_ERR, "AST already resolved.  !!! Not yet supported.\n");
		goto exit;
	}
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		rc = __cil_attrib_convert_type(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_typepermissive(struct cil_tree_node *node,
				     struct version_args *args __attribute__ ((unused)))
{
	struct cil_typepermissive *typeperm = (struct cil_typepermissive *)node->data;

	if (typeperm->type != NULL) {
		cil_log(CIL_ERR, "AST already resolved.  ### Not yet supported.\n");
		return SEPOL_ERR;
	}

	return SEPOL_OK;
}

static int cil_attrib_typeattribute(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_typeattribute *typeattr = (struct cil_typeattribute *)node->data;
	char *key = typeattr->datum.name;

	if (typeattr->types) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported (line %d).\n",
			node->line);
		goto exit;
	}
	if (!strncmp(key, "base_typeattr_", 14)) {
		rc = __cil_attrib_swap_symtab_key(node, key, args->num);
		if (rc != SEPOL_OK) {
			goto exit;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_typeattributeset(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_typeattributeset *typeattrset = (struct cil_typeattributeset *) node->data;

	if (typeattrset->datum_expr != NULL) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported (line %d).\n",
			node->line);
		goto exit;
	}

	key = typeattrset->attr_str;
	/* first check to see if the attribute to which this set belongs is versioned */
	if (!strncmp(key, "base_typeattr_", 14)) {
		typeattrset->attr_str = __cil_attrib_get_versname(key, args->num);
	}

	rc = cil_attrib_type_expr(typeattrset->str_expr, args);
	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_typealiasactual(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_aliasactual *aliasact = (struct cil_aliasactual *)node->data;

	key = aliasact->actual_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) != PLAT_NONE) {
		cil_log(CIL_ERR, "%s with platform public type not allowed (line %d)\n",
		    CIL_KEY_TYPEALIASACTUAL, node->line);
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_nametypetransition(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_nametypetransition *namettrans = (struct cil_nametypetransition *)node->data;

	if (namettrans->src != NULL) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported (line %d).\n",
			node->line);
		goto exit;
	}
	key = namettrans->src_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		namettrans->src_str = __cil_attrib_get_versname(key, args->num);
	}

	key = namettrans->tgt_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		namettrans->tgt_str = __cil_attrib_get_versname(key, args->num);
	}

	return SEPOL_OK;
exit:
	return rc;
}

/*
 * This is exactly the same as cil_attrib_nametypetransition, but the struct
 * layouts differ, so we can't reuse it.
 */
static int cil_attrib_type_rule(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_type_rule *type_rule = (struct cil_type_rule *)node->data;

	if (type_rule->src != NULL) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported (line %d).\n",
			node->line);
		goto exit;
	}
	key = type_rule->src_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		type_rule->src_str = __cil_attrib_get_versname(key, args->num);
	}

	key = type_rule->tgt_str;
	if (__cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		type_rule->tgt_str = __cil_attrib_get_versname(key, args->num);
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_avrule(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	char *key;
	struct cil_avrule *avrule = (struct cil_avrule *)node->data;

	if (avrule->src != NULL) {
		cil_log(CIL_ERR, "AST already resolved. Not yet supported (line %d).\n",
			node->line);
		goto exit;
	}

	key = avrule->src_str;
	if (!strncmp(key, "base_typeattr_", 14) ||
	    __cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		avrule->src_str = __cil_attrib_get_versname(key, args->num);
	}

	key = avrule->tgt_str;
	if (!strncmp(key, "base_typeattr_", 14) ||
	    __cil_get_plat_flavor(args->vers_map, (hashtab_key_t) key) == PLAT_TYPE) {
		avrule->tgt_str = __cil_attrib_get_versname(key, args->num);
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_genfscon(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;

	struct cil_genfscon *genfscon = (struct cil_genfscon *)node->data;

	if (genfscon->context_str == NULL) {
		/* genfscon contains an anon context, which needs to have type checked */
		rc = cil_attrib_check_context(genfscon->context, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int cil_attrib_fsuse(struct cil_tree_node *node, struct version_args *args)
{
	int rc = SEPOL_ERR;
	struct cil_fsuse *fsuse = (struct cil_fsuse *)node->data;

	if (fsuse->context_str == NULL) {
		/* fsuse contains an anon context, which needs to have type checked */
		rc = cil_attrib_check_context(fsuse->context, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
	}

	return SEPOL_OK;
exit:
	return rc;
}

static int __attributize_helper(struct cil_tree_node *node, uint32_t *finished, void *extra_args)
{
	int rc = SEPOL_ERR;
	struct version_args *args = (struct version_args *) extra_args;

	if (node == NULL || finished == NULL || extra_args == NULL) {
		goto exit;
	}

	switch (node->flavor) {
	case CIL_SIDCONTEXT:
		/* contains type, but shouldn't involve an attributized type, maybe add
		   a check on type and error if it conflicts */
		rc = cil_attrib_sidcontext(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_ROLE:
		cil_log(CIL_ERR, "%s declaration illegal non-platform policy (line %d)\n",
			CIL_KEY_ROLE, node->line);
		rc = SEPOL_ERR;
		break;
	case CIL_ROLETYPE:
		/* We shouldn't attributize roletype. system_ext/product public
		 * types can be missing in GSI, failing to assign roletype to
		 * such types. That will cause a compilation error for some
		 * statements like genfscon. */
		break;
	case CIL_ROLEATTRIBUTE:
		/* don't think this is needed, only used for cil_gen_req, and we aren't
		   yet supporting roles in non-platform policy. */
		break;
	case CIL_TYPE:
		/* conver to attribute if in policy */
		rc = cil_attrib_type(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_TYPEPERMISSIVE:
		rc = cil_attrib_typepermissive(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_TYPEATTRIBUTE:
		rc = cil_attrib_typeattribute(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_TYPEATTRIBUTESET:
		rc = cil_attrib_typeattributeset(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_TYPEALIASACTUAL:
		/* this will break on an attributized type - identify it and throw error */
		rc = cil_attrib_typealiasactual(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_NAMETYPETRANSITION:
		/* not allowed in plat-policy. Types present, throw error if attributee */
		rc = cil_attrib_nametypetransition(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_TYPE_RULE:
		/* not allowed in plat-policy. Types present, throw error if attributee */
		rc = cil_attrib_type_rule(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_AVRULE:
	case CIL_AVRULEX:
		rc = cil_attrib_avrule(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_CONTEXT:
		/* not currently found in AOSP policy, but if found would need to be
		   checked to not be attributee */
		rc = cil_attrib_context(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_GENFSCON:
		/* not allowed in plat-policy, but types present, throw error if attributee */
		rc = cil_attrib_genfscon(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_FILECON:
	case CIL_NODECON:
	case CIL_PORTCON:
	case CIL_PIRQCON:
	case CIL_IOMEMCON:
	case CIL_IOPORTCON:
	case CIL_PCIDEVICECON:
	case CIL_DEVICETREECON:
	case CIL_VALIDATETRANS:
	case CIL_MLSVALIDATETRANS:
	case CIL_CALL:
	case CIL_MACRO:
	case CIL_OPTIONAL:
		/* Not currently found in AOSP and not yet properly handled.  Return err until support added. */
		cil_log(CIL_ERR, "unsupported policy statement (line %d)\n", node->line);
		rc = SEPOL_ERR;
		goto exit;
	case CIL_FSUSE:
		/* not allowed in plat-policy, but types present, throw error if attributee */
		cil_attrib_fsuse(node, args);
		if (rc != SEPOL_OK) {
			goto exit;
		}
		break;
	case CIL_CONSTRAIN:
	case CIL_MLSCONSTRAIN:
		/* there is type info here, but not sure if we'll allow non-platform code
		   to have this, or whether or not it's in platform policy.  Currently
		   assuming that mlsconstrain is private-platform only, and that normal
		   constrain is verboten. */
		cil_log(CIL_ERR, "unsupported policy statement (line %d)\n", node->line);
		rc = SEPOL_ERR;
		goto exit;
	default:
		break;
	}

	return SEPOL_OK;
exit:
	return rc;
}

/*
 * walk ast, replacing previously identified types and attributes with the
 * attributized version. Also replace previous references to the attributees
 * with the versioned type.
 */
static int cil_attributize(struct cil_db *db, hashtab_t vers_map, const char *num)
{
	int rc = SEPOL_ERR;
	struct version_args extra_args;
	extra_args.db = db;
	extra_args.vers_map = vers_map;
	extra_args.num = num;

	rc = cil_tree_walk(db->ast->root, __attributize_helper, NULL, NULL, &extra_args);
	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

/*
 * Create typeattributeset mappings from the attributes generated from the
 * original types/attributes to the original values.  This mapping will provide
 * the basis for the platform policy's mapping to this public version.
 *
 * Add these new typeattributeset nodes to the given cil_db.
 */
static int cil_build_mappings_tree(hashtab_key_t k, hashtab_datum_t d, void *args)
{
	struct cil_typeattributeset *attrset = NULL;
	struct cil_typeattribute *typeattr = NULL;
	struct cil_expandtypeattribute *expandattr = NULL;
	struct cil_tree_node *ast_node = NULL;
	struct version_args *verargs = (struct version_args *)args;
	struct cil_tree_node *ast_parent = verargs->db->ast->root;
	char *orig_type = (char *) k;
	struct version_datum *vers_datum = (struct version_datum *) d;
	char *new_key = __cil_attrib_get_versname(orig_type, verargs->num);

	if (vers_datum->ast_node->flavor == CIL_TYPEATTRIBUTE) {
		// platform attributes are not versioned
		return SEPOL_OK;
	}
	/* create typeattributeset datum */
	cil_typeattributeset_init(&attrset);
	cil_list_init(&attrset->str_expr, CIL_TYPE);
	attrset->attr_str = new_key;
	cil_list_append(attrset->str_expr, CIL_STRING, orig_type);

	/* create containing tree node */
	cil_tree_node_init(&ast_node);
	ast_node->data = attrset;
	ast_node->flavor = CIL_TYPEATTRIBUTESET;

	/* add to tree */
	ast_node->parent = ast_parent;
	if (ast_parent->cl_head == NULL)
		ast_parent->cl_head = ast_node;
	else
		ast_parent->cl_tail->next = ast_node;
	ast_parent->cl_tail = ast_node;

	/* create expandtypeattribute datum */
	cil_expandtypeattribute_init(&expandattr);
	cil_list_init(&expandattr->attr_strs, CIL_TYPE);
	cil_list_append(expandattr->attr_strs, CIL_STRING, new_key);
	expandattr->expand = CIL_TRUE;

	/* create containing tree node */
	cil_tree_node_init(&ast_node);
	ast_node->data = expandattr;
	ast_node->flavor = CIL_EXPANDTYPEATTRIBUTE;
	/* add to tree */
	ast_node->parent = ast_parent;
	ast_parent->cl_tail->next = ast_node;
	ast_parent->cl_tail = ast_node;

	/* re)declare typeattribute. */
	cil_typeattribute_init(&typeattr);
	typeattr->datum.name = new_key;
	typeattr->datum.fqn = new_key;
	cil_tree_node_init(&ast_node);
	ast_node->data = typeattr;
	ast_node->flavor = CIL_TYPEATTRIBUTE;
	ast_node->parent = ast_parent;
	ast_parent->cl_tail->next = ast_node;
	ast_parent->cl_tail = ast_node;

	return SEPOL_OK;
}

/*
 * Initializes the given db and uses the version mapping generated by
 * cil_extract_attributees() to fill it with the glue policy required to
 * connect the attributized policy created by cil_attributize() to the policy
 * declaring the concrete types.
 */
static int cil_attrib_mapping(struct cil_db **db, hashtab_t vers_map, const char *num)
{
	int rc = SEPOL_ERR;
	struct version_args extra_args;

	cil_db_init(db);

	/* foreach entry in vers_map, create typeattributeset node and attach to tree */
	extra_args.db = *db;
	extra_args.vers_map = NULL;
	extra_args.num = num;
	rc = hashtab_map(vers_map, cil_build_mappings_tree, &extra_args);
	if (rc != SEPOL_OK) {
		goto exit;
	}

	return SEPOL_OK;
exit:
	return rc;
}

int cil_android_attrib_mapping(struct cil_db **mdb, struct cil_db *srcdb, const char *num)
{
	int rc = SEPOL_ERR;
	hashtab_t ver_map_tab = NULL;

	ver_map_tab = hashtab_create(ver_map_hash_val, ver_map_key_cmp, VER_MAP_SZ);
	if (!ver_map_tab) {
		cil_log(CIL_ERR, "Unable to create version mapping table.\n");
		goto exit;
	}
	rc = cil_build_ast(srcdb, srcdb->parse->root, srcdb->ast->root);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to build source db AST.\n");
		goto exit;
	}
	rc = cil_extract_attributees(srcdb, ver_map_tab);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to extract attributizable elements from source db.\n");
		goto exit;
	}
	rc = cil_attrib_mapping(mdb, ver_map_tab, num);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to create mapping db from source db.\n");
		goto exit;
	}
exit:
	ver_map_destroy(ver_map_tab);
	return rc;
}

int cil_android_attributize(struct cil_db *tgtdb, struct cil_db *srcdb, const char *num)
{
	int rc = SEPOL_ERR;
	hashtab_t ver_map_tab = NULL;

	ver_map_tab = hashtab_create(ver_map_hash_val, ver_map_key_cmp, VER_MAP_SZ);
	if (!ver_map_tab) {
		cil_log(CIL_ERR, "Unable to create version mapping table.\n");
		goto exit;
	}
	rc = cil_build_ast(srcdb, srcdb->parse->root, srcdb->ast->root);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to build source db AST.\n");
		goto exit;
	}
	rc = cil_extract_attributees(srcdb, ver_map_tab);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to extract attributizable elements from source db.\n");
		goto exit;
	}
	rc = cil_build_ast(tgtdb, tgtdb->parse->root, tgtdb->ast->root);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to build target db AST.\n");
		goto exit;
	}
	rc = cil_attributize(tgtdb, ver_map_tab, num);
	if (rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Unable to attributize target db.\n");
		goto exit;
	}
exit:
	ver_map_destroy(ver_map_tab);
	return rc;
}

static int define_symbols(struct cil_tree_node *node, uint32_t *finished __attribute__((unused)), void *extra_args)
{
	int rc = SEPOL_OK;
	struct cil_type *type = NULL;
	struct cil_typeattribute *typeattribute = NULL;
	struct policydb_amend_args *args = extra_args;
	policydb_t *pdb = args->pdb;
	void **type_value_to_cil = args->type_value_to_cil;

	switch (node->flavor) {
	case CIL_TYPE:
		rc = cil_type_to_policydb(pdb, node->data, type_value_to_cil);
		// symtab_insert returns 1 when the symbol already exists
		if (rc == 1) {
			type = node->data;
			cil_log(CIL_WARN, "Type with symbol \"%s\" already exists.\n", type->datum.fqn);
			rc = SEPOL_OK;
		}
		break;
	case CIL_TYPEATTRIBUTE:
		rc = cil_typeattribute_to_policydb(pdb, node->data, type_value_to_cil);
		// symtab_insert returns 1 when the symbol already exists
		if (rc == 1) {
			typeattribute = node->data;
			cil_log(CIL_WARN, "Typeattribute with symbol \"%s\" already exists.\n", typeattribute->datum.fqn);
			rc = SEPOL_OK;
		}
		break;
	case CIL_ROLE:
	case CIL_POLICYCAP:
	case CIL_USER:
	case CIL_BOOL:
	case CIL_CATALIAS:
	case CIL_SENS: // Unsupported symbol statements.
	default:
		break;
	}

	return rc;
}

static int expand_symbols(struct cil_tree_node *node, uint32_t *finished __attribute__((unused)), void *extra_args)
{
	int rc = SEPOL_OK;
	struct policydb_amend_args *args = extra_args;
	const struct cil_db *db = args->db;
	policydb_t *pdb = args->pdb;

	switch (node->flavor) {
	case CIL_TYPEATTRIBUTE:
		rc = cil_typeattribute_to_bitmap(pdb, db, node->data);
		break;
	case CIL_ROLE:
	case CIL_AVRULE:
	case CIL_AVRULEX:
	case CIL_TYPE:
	case CIL_TYPEALIAS:
	case CIL_TYPEPERMISSIVE:
	case CIL_SENSALIAS:
	case CIL_USER:
	case CIL_TYPE_RULE:
	case CIL_ROLETRANSITION:
	case CIL_ROLEATTRIBUTESET:
	case CIL_NAMETYPETRANSITION:
	case CIL_CONSTRAIN:
	case CIL_MLSCONSTRAIN:
	case CIL_VALIDATETRANS:
	case CIL_MLSVALIDATETRANS:
	case CIL_RANGETRANSITION:
	case CIL_DEFAULTUSER:
	case CIL_DEFAULTROLE:
	case CIL_DEFAULTTYPE:
	case CIL_DEFAULTRANGE: // Unsupported symbol statements.
	default:
		break;
	}

	return rc;
}

static int apply_rules(struct cil_tree_node *node, uint32_t *finished __attribute__((unused)), void *extra_args)
{
	int rc = SEPOL_OK;
	struct cil_avrule *rule = NULL;
	struct policydb_amend_args *args = extra_args;
	const struct cil_db *db = args->db;
	policydb_t *pdb = args->pdb;

	switch (node->flavor) {
	case CIL_AVRULE:
		rule = node->data;
		if (rule->rule_kind != CIL_AVRULE_NEVERALLOW) {
			rc = cil_avrule_to_policydb(pdb, db, node->data);
		}
		else {
			cil_log(CIL_WARN, "Found a neverallow rule.\n");
		}
		break;
	case CIL_BOOLEANIF:
	case CIL_AVRULEX:
	case CIL_ROLEALLOW: // Unsupported rule statement.
	default:
		break;
	}

	return rc;
}

int cil_amend_policydb(struct cil_db *db, sepol_policydb_t *policydb)
{
	int rc = SEPOL_ERR;
	policydb_t *pdb = &policydb->p;

	if (db == NULL || policydb == NULL) {
		if (db == NULL) {
			cil_log(CIL_ERR, "db == NULL\n");
		}
		else if (policydb == NULL) {
			cil_log(CIL_ERR, "policydb == NULL\n");
		}
		return rc;
	}

	// type_value_to_cil should be able to map the new types in the cil_db, the
	// existing types in the pdb, and the redefinitions from the cil_db.
	// Since libsepol values start at 1, we allocate extra memory instead of shifting every value.
	void **type_value_to_cil = calloc(db->num_types_and_attrs + pdb->p_types.nprim + 1,
									  sizeof(*type_value_to_cil));
	if (!type_value_to_cil)
		goto exit;

	struct policydb_amend_args extra_args;
	extra_args.db = db;
	extra_args.pdb = pdb;
	extra_args.type_value_to_cil = type_value_to_cil;

	// 1) Add types and attributes symbols to policy_db.
	unsigned int types_size = pdb->p_types.nprim;
	rc = cil_tree_walk(db->ast->root, define_symbols, NULL, NULL, &extra_args);
	if(rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Error adding symbols to policydb.\n");
		goto exit;
	}
	if (pdb->p_types.nprim > types_size) {
		// We introduced new types, we need to expand the type<->attr maps.
		pdb->type_attr_map = cil_realloc(pdb->type_attr_map, pdb->p_types.nprim * sizeof(ebitmap_t));
		pdb->attr_type_map = cil_realloc(pdb->attr_type_map, pdb->p_types.nprim * sizeof(ebitmap_t));
		for (unsigned int i = types_size; i < pdb->p_types.nprim; i++) {
			ebitmap_init(&pdb->type_attr_map[i]);
			ebitmap_init(&pdb->attr_type_map[i]);
		}
	}

	// 2) Expand typeattribute symbols.
	rc = cil_tree_walk(db->ast->root, expand_symbols, NULL, NULL, &extra_args);
	if(rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Error expanding symbols.\n");
		goto exit;
	}

	// 3) Apply rule statements.
	rc = cil_tree_walk(db->ast->root, apply_rules, NULL, NULL, &extra_args);
	if(rc != SEPOL_OK) {
		cil_log(CIL_ERR, "Error applying rules.\n");
		goto exit;
	}

exit:
	free(type_value_to_cil);
	return rc;
}
