#include "sepolicy.h"
#include "linux/gfp.h"
#include "linux/printk.h"
#include "linux/slab.h"
#include "linux/version.h"

#include "../klog.h" // IWYU pragma: keep
#include "ss/symtab.h"

#define KSU_SUPPORT_ADD_TYPE

/*
 * Adapt to Huawei HISI kernel without affecting other kernels ,
 * Huawei Hisi Kernel EBITMAP Enable or Disable Flag ,
 * From ss/ebitmap.h
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) &&                           \
	LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
#ifdef HISI_SELINUX_EBITMAP_RO
#define CONFIG_IS_HW_HISI
#endif
#endif

//////////////////////////////////////////////////////
// Declaration
//////////////////////////////////////////////////////

static struct avtab_node *get_avtab_node(struct policydb *db,
					 struct avtab_key *key,
					 struct avtab_extended_perms *xperms);

static bool add_rule(struct policydb *db, const char *s, const char *t,
		     const char *c, const char *p, int effect, bool invert);

static void add_rule_raw(struct policydb *db, struct type_datum *src,
			 struct type_datum *tgt, struct class_datum *cls,
			 struct perm_datum *perm, int effect, bool invert);

static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
			       struct type_datum *tgt, struct class_datum *cls,
			       uint16_t low, uint16_t high, int effect,
			       bool invert);
static bool add_xperm_rule(struct policydb *db, const char *s, const char *t,
			   const char *c, const char *range, int effect,
			   bool invert);

static bool add_type_rule(struct policydb *db, const char *s, const char *t,
			  const char *c, const char *d, int effect);

static bool add_filename_trans(struct policydb *db, const char *s,
			       const char *t, const char *c, const char *d,
			       const char *o);

static bool add_genfscon(struct policydb *db, const char *fs_name,
			 const char *path, const char *context);

static bool add_type(struct policydb *db, const char *type_name, bool attr);

static bool set_type_state(struct policydb *db, const char *type_name,
			   bool permissive);

static void add_typeattribute_raw(struct policydb *db, struct type_datum *type,
				  struct type_datum *attr);

static bool add_typeattribute(struct policydb *db, const char *type,
			      const char *attr);

//////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////

// Invert is adding rules for auditdeny; in other cases, invert is removing
// rules
#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)

#define ksu_hash_for_each(node_ptr, n_slot, cur)                               \
	int i;                                                                 \
	for (i = 0; i < n_slot; ++i)                                           \
		for (cur = node_ptr[i]; cur; cur = cur->next)

// htable is a struct instead of pointer above 5.8.0:
// https://elixir.bootlin.com/linux/v5.8-rc1/source/security/selinux/ss/symtab.h
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
#define ksu_hashtab_for_each(htab, cur)                                        \
	ksu_hash_for_each(htab.htable, htab.size, cur)
#else
#define ksu_hashtab_for_each(htab, cur)                                        \
	ksu_hash_for_each(htab->htable, htab->size, cur)
#endif

// symtab_search is introduced on 5.9.0:
// https://elixir.bootlin.com/linux/v5.9-rc1/source/security/selinux/ss/symtab.h
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 9, 0)
#define symtab_search(s, name) hashtab_search((s)->table, name)
#define symtab_insert(s, name, datum) hashtab_insert((s)->table, name, datum)
#endif

#define avtab_for_each(avtab, cur)                                             \
	ksu_hash_for_each(avtab.htable, avtab.nslot, cur);

static struct avtab_node *get_avtab_node(struct policydb *db,
					 struct avtab_key *key,
					 struct avtab_extended_perms *xperms)
{
	struct avtab_node *node;

	/* AVTAB_XPERMS entries are not necessarily unique */
	if (key->specified & AVTAB_XPERMS) {
		bool match = false;
		node = avtab_search_node(&db->te_avtab, key);
		while (node) {
			if ((node->datum.u.xperms->specified ==
			     xperms->specified) &&
			    (node->datum.u.xperms->driver == xperms->driver)) {
				match = true;
				break;
			}
			node = avtab_search_node_next(node, key->specified);
		}
		if (!match)
			node = NULL;
	} else {
		node = avtab_search_node(&db->te_avtab, key);
	}

	if (!node) {
		struct avtab_datum avdatum = {};
		/*
     * AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for
     * others. Initialize the data accordingly.
     */
		if (key->specified & AVTAB_XPERMS) {
			avdatum.u.xperms = xperms;
		} else {
			avdatum.u.data =
				key->specified == AVTAB_AUDITDENY ? ~0U : 0U;
		}
		/* this is used to get the node - insertion is actually unique */
		node = avtab_insert_nonunique(&db->te_avtab, key, &avdatum);

		int grow_size = sizeof(struct avtab_key);
		grow_size += sizeof(struct avtab_datum);
		if (key->specified & AVTAB_XPERMS) {
			grow_size += sizeof(u8);
			grow_size += sizeof(u8);
			grow_size += sizeof(u32) *
				     ARRAY_SIZE(avdatum.u.xperms->perms.p);
		}
		db->len += grow_size;
	}

	return node;
}

static bool add_rule(struct policydb *db, const char *s, const char *t,
		     const char *c, const char *p, int effect, bool invert)
{
	struct type_datum *src = NULL, *tgt = NULL;
	struct class_datum *cls = NULL;
	struct perm_datum *perm = NULL;

	if (s) {
		src = symtab_search(&db->p_types, s);
		if (src == NULL) {
			pr_info("source type %s does not exist\n", s);
			return false;
		}
	}

	if (t) {
		tgt = symtab_search(&db->p_types, t);
		if (tgt == NULL) {
			pr_info("target type %s does not exist\n", t);
			return false;
		}
	}

	if (c) {
		cls = symtab_search(&db->p_classes, c);
		if (cls == NULL) {
			pr_info("class %s does not exist\n", c);
			return false;
		}
	}

	if (p) {
		if (c == NULL) {
			pr_info("No class is specified, cannot add perm [%s] \n",
				p);
			return false;
		}

		perm = symtab_search(&cls->permissions, p);
		if (perm == NULL && cls->comdatum != NULL) {
			perm = symtab_search(&cls->comdatum->permissions, p);
		}
		if (perm == NULL) {
			pr_info("perm %s does not exist in class %s\n", p, c);
			return false;
		}
	}
	add_rule_raw(db, src, tgt, cls, perm, effect, invert);
	return true;
}

static void add_rule_raw(struct policydb *db, struct type_datum *src,
			 struct type_datum *tgt, struct class_datum *cls,
			 struct perm_datum *perm, int effect, bool invert)
{
	if (src == NULL) {
		struct hashtab_node *node;
		if (strip_av(effect, invert)) {
			ksu_hashtab_for_each(db->p_types.table, node)
			{
				add_rule_raw(db,
					     (struct type_datum *)node->datum,
					     tgt, cls, perm, effect, invert);
			};
		} else {
			ksu_hashtab_for_each(db->p_types.table, node)
			{
				struct type_datum *type =
					(struct type_datum *)(node->datum);
				if (type->attribute) {
					add_rule_raw(db, type, tgt, cls, perm,
						     effect, invert);
				}
			};
		}
	} else if (tgt == NULL) {
		struct hashtab_node *node;
		if (strip_av(effect, invert)) {
			ksu_hashtab_for_each(db->p_types.table, node)
			{
				add_rule_raw(db, src,
					     (struct type_datum *)node->datum,
					     cls, perm, effect, invert);
			};
		} else {
			ksu_hashtab_for_each(db->p_types.table, node)
			{
				struct type_datum *type =
					(struct type_datum *)(node->datum);
				if (type->attribute) {
					add_rule_raw(db, src, type, cls, perm,
						     effect, invert);
				}
			};
		}
	} else if (cls == NULL) {
		struct hashtab_node *node;
		ksu_hashtab_for_each(db->p_classes.table, node)
		{
			add_rule_raw(db, src, tgt,
				     (struct class_datum *)node->datum, perm,
				     effect, invert);
		}
	} else {
		struct avtab_key key;
		key.source_type = src->value;
		key.target_type = tgt->value;
		key.target_class = cls->value;
		key.specified = effect;

		struct avtab_node *node = get_avtab_node(db, &key, NULL);
		if (invert) {
			if (perm)
				node->datum.u.data &=
					~(1U << (perm->value - 1));
			else
				node->datum.u.data = 0U;
		} else {
			if (perm)
				node->datum.u.data |= 1U << (perm->value - 1);
			else
				node->datum.u.data = ~0U;
		}
	}
}

#define ioctl_driver(x) (x >> 8 & 0xFF)
#define ioctl_func(x) (x & 0xFF)

#define xperm_test(x, p) (1 & (p[x >> 5] >> (x & 0x1f)))
#define xperm_set(x, p) (p[x >> 5] |= (1 << (x & 0x1f)))
#define xperm_clear(x, p) (p[x >> 5] &= ~(1 << (x & 0x1f)))

static void add_xperm_rule_raw(struct policydb *db, struct type_datum *src,
			       struct type_datum *tgt, struct class_datum *cls,
			       uint16_t low, uint16_t high, int effect,
			       bool invert)
{
	if (src == NULL) {
		struct hashtab_node *node;
		ksu_hashtab_for_each(db->p_types.table, node)
		{
			struct type_datum *type =
				(struct type_datum *)(node->datum);
			if (type->attribute) {
				add_xperm_rule_raw(db, type, tgt, cls, low,
						   high, effect, invert);
			}
		};
	} else if (tgt == NULL) {
		struct hashtab_node *node;
		ksu_hashtab_for_each(db->p_types.table, node)
		{
			struct type_datum *type =
				(struct type_datum *)(node->datum);
			if (type->attribute) {
				add_xperm_rule_raw(db, src, type, cls, low,
						   high, effect, invert);
			}
		};
	} else if (cls == NULL) {
		struct hashtab_node *node;
		ksu_hashtab_for_each(db->p_classes.table, node)
		{
			add_xperm_rule_raw(db, src, tgt,
					   (struct class_datum *)(node->datum),
					   low, high, effect, invert);
		};
	} else {
		struct avtab_key key;
		key.source_type = src->value;
		key.target_type = tgt->value;
		key.target_class = cls->value;
		key.specified = effect;

		struct avtab_datum *datum;
		struct avtab_node *node;
		struct avtab_extended_perms xperms;

		memset(&xperms, 0, sizeof(xperms));
		if (ioctl_driver(low) != ioctl_driver(high)) {
			xperms.specified = AVTAB_XPERMS_IOCTLDRIVER;
			xperms.driver = 0;
		} else {
			xperms.specified = AVTAB_XPERMS_IOCTLFUNCTION;
			xperms.driver = ioctl_driver(low);
		}
		int i;
		if (xperms.specified == AVTAB_XPERMS_IOCTLDRIVER) {
			for (i = ioctl_driver(low); i <= ioctl_driver(high);
			     ++i) {
				if (invert)
					xperm_clear(i, xperms.perms.p);
				else
					xperm_set(i, xperms.perms.p);
			}
		} else {
			for (i = ioctl_func(low); i <= ioctl_func(high); ++i) {
				if (invert)
					xperm_clear(i, xperms.perms.p);
				else
					xperm_set(i, xperms.perms.p);
			}
		}

		node = get_avtab_node(db, &key, &xperms);
		if (!node) {
			pr_warn("add_xperm_rule_raw cannot found node!\n");
			return;
		}
		datum = &node->datum;

		if (datum->u.xperms == NULL) {
			datum->u.xperms =
				(struct avtab_extended_perms *)(kmalloc(
					sizeof(xperms), GFP_KERNEL));
			if (!datum->u.xperms) {
				pr_err("alloc xperms failed\n");
				return;
			}
			memcpy(datum->u.xperms, &xperms, sizeof(xperms));
		}
	}
}

static bool add_xperm_rule(struct policydb *db, const char *s, const char *t,
			   const char *c, const char *range, int effect,
			   bool invert)
{
	struct type_datum *src = NULL, *tgt = NULL;
	struct class_datum *cls = NULL;

	if (s) {
		src = symtab_search(&db->p_types, s);
		if (src == NULL) {
			pr_info("source type %s does not exist\n", s);
			return false;
		}
	}

	if (t) {
		tgt = symtab_search(&db->p_types, t);
		if (tgt == NULL) {
			pr_info("target type %s does not exist\n", t);
			return false;
		}
	}

	if (c) {
		cls = symtab_search(&db->p_classes, c);
		if (cls == NULL) {
			pr_info("class %s does not exist\n", c);
			return false;
		}
	}

	u16 low, high;

	if (range) {
		if (strchr(range, '-')) {
			sscanf(range, "%hx-%hx", &low, &high);
		} else {
			sscanf(range, "%hx", &low);
			high = low;
		}
	} else {
		low = 0;
		high = 0xFFFF;
	}

	add_xperm_rule_raw(db, src, tgt, cls, low, high, effect, invert);
	return true;
}

static bool add_type_rule(struct policydb *db, const char *s, const char *t,
			  const char *c, const char *d, int effect)
{
	struct type_datum *src, *tgt, *def;
	struct class_datum *cls;

	src = symtab_search(&db->p_types, s);
	if (src == NULL) {
		pr_info("source type %s does not exist\n", s);
		return false;
	}
	tgt = symtab_search(&db->p_types, t);
	if (tgt == NULL) {
		pr_info("target type %s does not exist\n", t);
		return false;
	}
	cls = symtab_search(&db->p_classes, c);
	if (cls == NULL) {
		pr_info("class %s does not exist\n", c);
		return false;
	}
	def = symtab_search(&db->p_types, d);
	if (def == NULL) {
		pr_info("default type %s does not exist\n", d);
		return false;
	}

	struct avtab_key key;
	key.source_type = src->value;
	key.target_type = tgt->value;
	key.target_class = cls->value;
	key.specified = effect;

	struct avtab_node *node = get_avtab_node(db, &key, NULL);
	node->datum.u.data = def->value;

	return true;
}

// 5.9.0 : static inline int hashtab_insert(struct hashtab *h, void *key, void
// *datum, struct hashtab_key_params key_params) 5.8.0: int
// hashtab_insert(struct hashtab *h, void *k, void *d);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
static u32 filenametr_hash(const void *k)
{
	const struct filename_trans_key *ft = k;
	unsigned long hash;
	unsigned int byte_num;
	unsigned char focus;

	hash = ft->ttype ^ ft->tclass;

	byte_num = 0;
	while ((focus = ft->name[byte_num++]))
		hash = partial_name_hash(focus, hash);
	return hash;
}

static int filenametr_cmp(const void *k1, const void *k2)
{
	const struct filename_trans_key *ft1 = k1;
	const struct filename_trans_key *ft2 = k2;
	int v;

	v = ft1->ttype - ft2->ttype;
	if (v)
		return v;

	v = ft1->tclass - ft2->tclass;
	if (v)
		return v;

	return strcmp(ft1->name, ft2->name);
}

static const struct hashtab_key_params filenametr_key_params = {
	.hash = filenametr_hash,
	.cmp = filenametr_cmp,
};
#endif

static bool add_filename_trans(struct policydb *db, const char *s,
			       const char *t, const char *c, const char *d,
			       const char *o)
{
	struct type_datum *src, *tgt, *def;
	struct class_datum *cls;

	src = symtab_search(&db->p_types, s);
	if (src == NULL) {
		pr_warn("source type %s does not exist\n", s);
		return false;
	}
	tgt = symtab_search(&db->p_types, t);
	if (tgt == NULL) {
		pr_warn("target type %s does not exist\n", t);
		return false;
	}
	cls = symtab_search(&db->p_classes, c);
	if (cls == NULL) {
		pr_warn("class %s does not exist\n", c);
		return false;
	}
	def = symtab_search(&db->p_types, d);
	if (def == NULL) {
		pr_warn("default type %s does not exist\n", d);
		return false;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
	struct filename_trans_key key;
	key.ttype = tgt->value;
	key.tclass = cls->value;
	key.name = (char *)o;

	struct filename_trans_datum *last = NULL;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
	struct filename_trans_datum *trans =
		policydb_filenametr_search(db, &key);
#else
	struct filename_trans_datum *trans =
		hashtab_search(&db->filename_trans, &key);
#endif
	while (trans) {
		if (ebitmap_get_bit(&trans->stypes, src->value - 1)) {
			// Duplicate, overwrite existing data and return
			trans->otype = def->value;
			return true;
		}
		if (trans->otype == def->value)
			break;
		last = trans;
		trans = trans->next;
	}

	if (trans == NULL) {
		trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans),
							       1, GFP_ATOMIC);
		struct filename_trans_key *new_key =
			(struct filename_trans_key *)kmalloc(sizeof(*new_key),
							     GFP_ATOMIC);
		*new_key = key;
		new_key->name = kstrdup(key.name, GFP_ATOMIC);
		trans->next = last;
		trans->otype = def->value;
		hashtab_insert(&db->filename_trans, new_key, trans,
			       filenametr_key_params);
	}

	db->compat_filename_trans_count++;
	return ebitmap_set_bit(&trans->stypes, src->value - 1, 1) == 0;
#else // < 5.7.0, has no filename_trans_key, but struct filename_trans

	struct filename_trans key;
	key.ttype = tgt->value;
	key.tclass = cls->value;
	key.name = (char *)o;

	struct filename_trans_datum *trans =
		hashtab_search(db->filename_trans, &key);

	if (trans == NULL) {
		trans = (struct filename_trans_datum *)kcalloc(sizeof(*trans),
							       1, GFP_ATOMIC);
		if (!trans) {
			pr_err("add_filename_trans: Failed to alloc datum\n");
			return false;
		}
		struct filename_trans *new_key =
			(struct filename_trans *)kmalloc(sizeof(*new_key),
							 GFP_ATOMIC);
		if (!new_key) {
			pr_err("add_filename_trans: Failed to alloc new_key\n");
			return false;
		}
		*new_key = key;
		new_key->name = kstrdup(key.name, GFP_ATOMIC);
		trans->otype = def->value;
		hashtab_insert(db->filename_trans, new_key, trans);
	}

	return ebitmap_set_bit(&db->filename_trans_ttypes, src->value - 1, 1) ==
	       0;
#endif
}

static bool add_genfscon(struct policydb *db, const char *fs_name,
			 const char *path, const char *context)
{
	return false;
}

static bool add_type(struct policydb *db, const char *type_name, bool attr)
{
#ifdef KSU_SUPPORT_ADD_TYPE
	struct type_datum *type = symtab_search(&db->p_types, type_name);
	if (type) {
		pr_warn("Type %s already exists\n", type_name);
		return true;
	}

	u32 value = ++db->p_types.nprim;
	type = (struct type_datum *)kzalloc(sizeof(struct type_datum),
					    GFP_ATOMIC);
	if (!type) {
		pr_err("add_type: alloc type_datum failed.\n");
		return false;
	}

	type->primary = 1;
	type->value = value;
	type->attribute = attr;

	char *key = kstrdup(type_name, GFP_ATOMIC);
	if (!key) {
		pr_err("add_type: alloc key failed.\n");
		return false;
	}

	if (symtab_insert(&db->p_types, key, type)) {
		pr_err("add_type: insert symtab failed.\n");
		return false;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
	size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim;
	struct ebitmap *new_type_attr_map_array =
		(krealloc(db->type_attr_map_array, new_size, GFP_ATOMIC));

	struct type_datum **new_type_val_to_struct =
		krealloc(db->type_val_to_struct,
			 sizeof(*db->type_val_to_struct) * db->p_types.nprim,
			 GFP_ATOMIC);

	if (!new_type_attr_map_array) {
		pr_err("add_type: alloc type_attr_map_array failed\n");
		return false;
	}

	if (!new_type_val_to_struct) {
		pr_err("add_type: alloc type_val_to_struct failed\n");
		return false;
	}

	char **new_val_to_name_types =
		krealloc(db->sym_val_to_name[SYM_TYPES],
			 sizeof(char *) * db->symtab[SYM_TYPES].nprim,
			 GFP_KERNEL);
	if (!new_val_to_name_types) {
		pr_err("add_type: alloc val_to_name failed\n");
		return false;
	}

	db->type_attr_map_array = new_type_attr_map_array;
	ebitmap_init(&db->type_attr_map_array[value - 1]);
	ebitmap_set_bit(&db->type_attr_map_array[value - 1], value - 1, 1);

	db->type_val_to_struct = new_type_val_to_struct;
	db->type_val_to_struct[value - 1] = type;

	db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types;
	db->sym_val_to_name[SYM_TYPES][value - 1] = key;

	int i;
	for (i = 0; i < db->p_roles.nprim; ++i) {
		ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
				1);
	}

	return true;
#elif defined(CONFIG_IS_HW_HISI)
	/*
   * Huawei use type_attr_map and type_val_to_struct.
   * And use ebitmap not flex_array.
   */
	size_t new_size = sizeof(struct ebitmap) * db->p_types.nprim;
	struct ebitmap *new_type_attr_map =
		(krealloc(db->type_attr_map, new_size, GFP_ATOMIC));

	struct type_datum **new_type_val_to_struct =
		krealloc(db->type_val_to_struct,
			 sizeof(*db->type_val_to_struct) * db->p_types.nprim,
			 GFP_ATOMIC);

	if (!new_type_attr_map) {
		pr_err("add_type: alloc type_attr_map failed\n");
		return false;
	}

	if (!new_type_val_to_struct) {
		pr_err("add_type: alloc type_val_to_struct failed\n");
		return false;
	}

	char **new_val_to_name_types =
		krealloc(db->sym_val_to_name[SYM_TYPES],
			 sizeof(char *) * db->symtab[SYM_TYPES].nprim,
			 GFP_KERNEL);
	if (!new_val_to_name_types) {
		pr_err("add_type: alloc val_to_name failed\n");
		return false;
	}

	db->type_attr_map = new_type_attr_map;
	ebitmap_init(&db->type_attr_map[value - 1], HISI_SELINUX_EBITMAP_RO);
	ebitmap_set_bit(&db->type_attr_map[value - 1], value - 1, 1);

	db->type_val_to_struct = new_type_val_to_struct;
	db->type_val_to_struct[value - 1] = type;

	db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types;
	db->sym_val_to_name[SYM_TYPES][value - 1] = key;

	int i;
	for (i = 0; i < db->p_roles.nprim; ++i) {
		ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
				1);
	}

	return true;
#else
	// flex_array is not extensible, we need to create a new bigger one instead
	struct flex_array *new_type_attr_map_array =
		flex_array_alloc(sizeof(struct ebitmap), db->p_types.nprim,
				 GFP_ATOMIC | __GFP_ZERO);

	struct flex_array *new_type_val_to_struct =
		flex_array_alloc(sizeof(struct type_datum *), db->p_types.nprim,
				 GFP_ATOMIC | __GFP_ZERO);

	struct flex_array *new_val_to_name_types =
		flex_array_alloc(sizeof(char *), db->symtab[SYM_TYPES].nprim,
				 GFP_ATOMIC | __GFP_ZERO);

	if (!new_type_attr_map_array) {
		pr_err("add_type: alloc type_attr_map_array failed\n");
		return false;
	}

	if (!new_type_val_to_struct) {
		pr_err("add_type: alloc type_val_to_struct failed\n");
		return false;
	}

	if (!new_val_to_name_types) {
		pr_err("add_type: alloc val_to_name failed\n");
		return false;
	}

	// preallocate so we don't have to worry about the put ever failing
	if (flex_array_prealloc(new_type_attr_map_array, 0, db->p_types.nprim,
				GFP_ATOMIC | __GFP_ZERO)) {
		pr_err("add_type: prealloc type_attr_map_array failed\n");
		return false;
	}

	if (flex_array_prealloc(new_type_val_to_struct, 0, db->p_types.nprim,
				GFP_ATOMIC | __GFP_ZERO)) {
		pr_err("add_type: prealloc type_val_to_struct_array failed\n");
		return false;
	}

	if (flex_array_prealloc(new_val_to_name_types, 0,
				db->symtab[SYM_TYPES].nprim,
				GFP_ATOMIC | __GFP_ZERO)) {
		pr_err("add_type: prealloc val_to_name_types failed\n");
		return false;
	}

	int j;
	void *old_elem;
	// copy the old data or pointers to new flex arrays
	for (j = 0; j < db->type_attr_map_array->total_nr_elements; j++) {
		old_elem = flex_array_get(db->type_attr_map_array, j);
		if (old_elem)
			flex_array_put(new_type_attr_map_array, j, old_elem,
				       GFP_ATOMIC | __GFP_ZERO);
	}

	for (j = 0; j < db->type_val_to_struct_array->total_nr_elements; j++) {
		old_elem = flex_array_get_ptr(db->type_val_to_struct_array, j);
		if (old_elem)
			flex_array_put_ptr(new_type_val_to_struct, j, old_elem,
					   GFP_ATOMIC | __GFP_ZERO);
	}

	for (j = 0; j < db->symtab[SYM_TYPES].nprim; j++) {
		old_elem =
			flex_array_get_ptr(db->sym_val_to_name[SYM_TYPES], j);
		if (old_elem)
			flex_array_put_ptr(new_val_to_name_types, j, old_elem,
					   GFP_ATOMIC | __GFP_ZERO);
	}

	// store the pointer of old flex arrays first, when assigning new ones we
	// should free it
	struct flex_array *old_fa;

	old_fa = db->type_attr_map_array;
	db->type_attr_map_array = new_type_attr_map_array;
	if (old_fa) {
		flex_array_free(old_fa);
	}

	ebitmap_init(flex_array_get(db->type_attr_map_array, value - 1));
	ebitmap_set_bit(flex_array_get(db->type_attr_map_array, value - 1),
			value - 1, 1);

	old_fa = db->type_val_to_struct_array;
	db->type_val_to_struct_array = new_type_val_to_struct;
	if (old_fa) {
		flex_array_free(old_fa);
	}
	flex_array_put_ptr(db->type_val_to_struct_array, value - 1, type,
			   GFP_ATOMIC | __GFP_ZERO);

	old_fa = db->sym_val_to_name[SYM_TYPES];
	db->sym_val_to_name[SYM_TYPES] = new_val_to_name_types;
	if (old_fa) {
		flex_array_free(old_fa);
	}
	flex_array_put_ptr(db->sym_val_to_name[SYM_TYPES], value - 1, key,
			   GFP_ATOMIC | __GFP_ZERO);

	int i;
	for (i = 0; i < db->p_roles.nprim; ++i) {
		ebitmap_set_bit(&db->role_val_to_struct[i]->types, value - 1,
				1);
	}
	return true;
#endif

#else
	return false;
#endif
}

static bool set_type_state(struct policydb *db, const char *type_name,
			   bool permissive)
{
	struct type_datum *type;
	if (type_name == NULL) {
		struct hashtab_node *node;
		ksu_hashtab_for_each(db->p_types.table, node)
		{
			type = (struct type_datum *)(node->datum);
			if (ebitmap_set_bit(&db->permissive_map, type->value,
					    permissive))
				pr_info("Could not set bit in permissive map\n");
		};
	} else {
		type = (struct type_datum *)symtab_search(&db->p_types,
							  type_name);
		if (type == NULL) {
			pr_info("type %s does not exist\n", type_name);
			return false;
		}
		if (ebitmap_set_bit(&db->permissive_map, type->value,
				    permissive)) {
			pr_info("Could not set bit in permissive map\n");
			return false;
		}
	}
	return true;
}

static void add_typeattribute_raw(struct policydb *db, struct type_datum *type,
				  struct type_datum *attr)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0)
	struct ebitmap *sattr = &db->type_attr_map_array[type->value - 1];
#elif defined(CONFIG_IS_HW_HISI)
	/*
   *   HISI_SELINUX_EBITMAP_RO is Huawei's unique features.
   */
	struct ebitmap *sattr = &db->type_attr_map[type->value - 1],
		       HISI_SELINUX_EBITMAP_RO;
#else
	struct ebitmap *sattr =
		flex_array_get(db->type_attr_map_array, type->value - 1);
#endif
	ebitmap_set_bit(sattr, attr->value - 1, 1);

	struct hashtab_node *node;
	struct constraint_node *n;
	struct constraint_expr *e;
	ksu_hashtab_for_each(db->p_classes.table, node)
	{
		struct class_datum *cls = (struct class_datum *)(node->datum);
		for (n = cls->constraints; n; n = n->next) {
			for (e = n->expr; e; e = e->next) {
				if (e->expr_type == CEXPR_NAMES &&
				    ebitmap_get_bit(&e->type_names->types,
						    attr->value - 1)) {
					ebitmap_set_bit(&e->names,
							type->value - 1, 1);
				}
			}
		}
	};
}

static bool add_typeattribute(struct policydb *db, const char *type,
			      const char *attr)
{
	struct type_datum *type_d = symtab_search(&db->p_types, type);
	if (type_d == NULL) {
		pr_info("type %s does not exist\n", type);
		return false;
	} else if (type_d->attribute) {
		pr_info("type %s is an attribute\n", attr);
		return false;
	}

	struct type_datum *attr_d = symtab_search(&db->p_types, attr);
	if (attr_d == NULL) {
		pr_info("attribute %s does not exist\n", type);
		return false;
	} else if (!attr_d->attribute) {
		pr_info("type %s is not an attribute \n", attr);
		return false;
	}

	add_typeattribute_raw(db, type_d, attr_d);
	return true;
}

//////////////////////////////////////////////////////////////////////////

// Operation on types
bool ksu_type(struct policydb *db, const char *name, const char *attr)
{
	return add_type(db, name, false) && add_typeattribute(db, name, attr);
}

bool ksu_attribute(struct policydb *db, const char *name)
{
	return add_type(db, name, true);
}

bool ksu_permissive(struct policydb *db, const char *type)
{
	return set_type_state(db, type, true);
}

bool ksu_enforce(struct policydb *db, const char *type)
{
	return set_type_state(db, type, false);
}

bool ksu_typeattribute(struct policydb *db, const char *type, const char *attr)
{
	return add_typeattribute(db, type, attr);
}

bool ksu_exists(struct policydb *db, const char *type)
{
	return symtab_search(&db->p_types, type) != NULL;
}

// Access vector rules
bool ksu_allow(struct policydb *db, const char *src, const char *tgt,
	       const char *cls, const char *perm)
{
	return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, false);
}

bool ksu_deny(struct policydb *db, const char *src, const char *tgt,
	      const char *cls, const char *perm)
{
	return add_rule(db, src, tgt, cls, perm, AVTAB_ALLOWED, true);
}

bool ksu_auditallow(struct policydb *db, const char *src, const char *tgt,
		    const char *cls, const char *perm)
{
	return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITALLOW, false);
}
bool ksu_dontaudit(struct policydb *db, const char *src, const char *tgt,
		   const char *cls, const char *perm)
{
	return add_rule(db, src, tgt, cls, perm, AVTAB_AUDITDENY, true);
}

// Extended permissions access vector rules
bool ksu_allowxperm(struct policydb *db, const char *src, const char *tgt,
		    const char *cls, const char *range)
{
	return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_ALLOWED,
			      false);
}

bool ksu_auditallowxperm(struct policydb *db, const char *src, const char *tgt,
			 const char *cls, const char *range)
{
	return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_AUDITALLOW,
			      false);
}

bool ksu_dontauditxperm(struct policydb *db, const char *src, const char *tgt,
			const char *cls, const char *range)
{
	return add_xperm_rule(db, src, tgt, cls, range, AVTAB_XPERMS_DONTAUDIT,
			      false);
}

// Type rules
bool ksu_type_transition(struct policydb *db, const char *src, const char *tgt,
			 const char *cls, const char *def, const char *obj)
{
	if (obj) {
		return add_filename_trans(db, src, tgt, cls, def, obj);
	} else {
		return add_type_rule(db, src, tgt, cls, def, AVTAB_TRANSITION);
	}
}

bool ksu_type_change(struct policydb *db, const char *src, const char *tgt,
		     const char *cls, const char *def)
{
	return add_type_rule(db, src, tgt, cls, def, AVTAB_CHANGE);
}

bool ksu_type_member(struct policydb *db, const char *src, const char *tgt,
		     const char *cls, const char *def)
{
	return add_type_rule(db, src, tgt, cls, def, AVTAB_MEMBER);
}

// File system labeling
bool ksu_genfscon(struct policydb *db, const char *fs_name, const char *path,
		  const char *ctx)
{
	return add_genfscon(db, fs_name, path, ctx);
}
