/* Authors: Steve Lawrence <slawrence@tresys.com>
 *
 * Functions to convert policy module to CIL
 *
 * Copyright (C) 2015 Tresys Technology, LLC
 * Copyright (C) 2017 Mellanox Technologies Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <netinet/in.h>
#ifndef IPPROTO_DCCP
#define IPPROTO_DCCP 33
#endif
#ifndef IPPROTO_SCTP
#define IPPROTO_SCTP 132
#endif
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <sepol/module.h>
#include <sepol/module_to_cil.h>
#include <sepol/policydb/conditional.h>
#include <sepol/policydb/hashtab.h>
#include <sepol/policydb/polcaps.h>
#include <sepol/policydb/policydb.h>
#include <sepol/policydb/services.h>
#include <sepol/policydb/util.h>

#include "kernel_to_common.h"
#include "private.h"
#include "module_internal.h"

#ifdef __GNUC__
#  define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
#else
#  define UNUSED(x) UNUSED_ ## x
#endif

static FILE *out_file;

#define STACK_SIZE 16
#define DEFAULT_LEVEL "systemlow"
#define DEFAULT_OBJECT "object_r"
#define GEN_REQUIRE_ATTR "cil_gen_require" /* Also in libsepol/cil/src/cil_post.c */
#define TYPEATTR_INFIX "_typeattr_"        /* Also in libsepol/cil/src/cil_post.c */
#define ROLEATTR_INFIX "_roleattr_"

__attribute__ ((format(printf, 1, 2)))
static void log_err(const char *fmt, ...)
{
	va_list argptr;
	va_start(argptr, fmt);
	if (vfprintf(stderr, fmt, argptr) < 0) {
		_exit(EXIT_FAILURE);
	}
	va_end(argptr);
	if (fprintf(stderr, "\n") < 0) {
		_exit(EXIT_FAILURE);
	}
}

static void cil_indent(int indent)
{
	if (fprintf(out_file, "%*s", indent * 4, "") < 0) {
		log_err("Failed to write to output");
		_exit(EXIT_FAILURE);
	}
}

__attribute__ ((format(printf, 1, 2)))
static void cil_printf(const char *fmt, ...) {
	va_list argptr;
	va_start(argptr, fmt);
	if (vfprintf(out_file, fmt, argptr) < 0) {
		log_err("Failed to write to output");
		_exit(EXIT_FAILURE);
	}
	va_end(argptr);
}

__attribute__ ((format(printf, 2, 3)))
static void cil_println(int indent, const char *fmt, ...)
{
	va_list argptr;
	cil_indent(indent);
	va_start(argptr, fmt);
	if (vfprintf(out_file, fmt, argptr) < 0) {
		log_err("Failed to write to output");
		_exit(EXIT_FAILURE);
	}
	va_end(argptr);
	if (fprintf(out_file, "\n") < 0) {
		log_err("Failed to write to output");
		_exit(EXIT_FAILURE);
	}
}

static int get_line(char **start, char *end, char **line)
{
	int rc = 1;
	char *p = NULL;
	size_t len = 0;

	*line = NULL;

	for (p = *start; p < end && isspace(*p); p++);

	*start = p;

	for (len = 0; p < end && *p != '\n' && *p != '\0'; p++, len++);

	if (zero_or_saturated(len)) {
		rc = 0;
		goto exit;
	}

	*line = malloc(len+1);
	if (*line == NULL) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}

	memcpy(*line, *start, len);
	(*line)[len] = '\0';

	*start = p;

	return rc;

exit:
	*start = NULL;
	return rc;
}

struct map_args {
	struct policydb *pdb;
	struct avrule_block *block;
	struct stack *decl_stack;
	int scope;
	int indent;
	int sym_index;
};

struct stack {
	 void **stack;
	 int pos;
	 int size;
};

struct role_list_node {
	char *role_name;
	role_datum_t *role;
};

struct attr_list_node {
	char *attr_name;
	int is_type;
	void *set;
};

struct list_node {
	void *data;
	struct list_node *next;
};

struct list {
	struct list_node *head;
};

/* A linked list of all roles stored in the pdb
 * which is iterated to determine types associated
 * with each role when printing role_type statements
 */
static struct list *role_list;

static void list_destroy(struct list **list)
{
	struct list_node *curr = (*list)->head;
	struct list_node *tmp;

	while (curr != NULL) {
		tmp = curr->next;
		free(curr);
		curr = tmp;
	}

	free(*list);
	*list = NULL;
}

static void role_list_destroy(void)
{
	struct list_node *curr;

	if (role_list == NULL) {
		return;
	}
	curr = role_list->head;

	while (curr != NULL) {
		free(curr->data);
		curr->data = NULL;
		curr = curr->next;
	}

	list_destroy(&role_list);
}

static void attr_list_destroy(struct list **attr_list)
{
	struct list_node *curr;
	struct attr_list_node *attr;

	if (attr_list == NULL || *attr_list == NULL) {
		return;
	}

	curr = (*attr_list)->head;

	while (curr != NULL) {
		attr = curr->data;
		if (attr != NULL) {
			free(attr->attr_name);
		}

		free(curr->data);
		curr->data = NULL;
		curr = curr->next;
	}

	list_destroy(attr_list);
}

static int list_init(struct list **list)
{
	struct list *l = calloc(1, sizeof(*l));
	if (l == NULL) {
		return -1;
	}

	*list = l;
	return 0;
}

static int list_prepend(struct list *list, void *data)
{
	int rc = -1;
	struct list_node *node = calloc(1, sizeof(*node));
	if (node == NULL) {
		goto exit;
	}

	node->data = data;
	node->next = list->head;
	list->head = node;

	rc = 0;

exit:
	return rc;
}

static int roles_gather_map(char *key, void *data, void *args)
{
	struct role_list_node *role_node;
	role_datum_t *role = data;
	int rc = -1;

	role_node = calloc(1, sizeof(*role_node));
	if (role_node == NULL) {
		return rc;
	}

	role_node->role_name = key;
	role_node->role = role;

	rc = list_prepend((struct list *)args, role_node);
	if (rc != 0)
		free(role_node);
	return rc;
}

static int role_list_create(hashtab_t roles_tab)
{
	int rc = -1;

	rc = list_init(&role_list);
	if (rc != 0) {
		goto exit;
	}

	rc = hashtab_map(roles_tab, roles_gather_map, role_list);

exit:
	return rc;
}

// array of lists, where each list contains all the aliases defined in the scope at index i
static struct list **typealias_lists;
static uint32_t typealias_lists_len;

static int typealiases_gather_map(char *key, void *data, void *arg)
{
	int rc = -1;
	struct type_datum *type = data;
	struct policydb *pdb = arg;
	struct scope_datum *scope;
	uint32_t len;
	uint32_t scope_id;

	if (type->primary != 1) {
		scope = hashtab_search(pdb->scope[SYM_TYPES].table, key);
		if (scope == NULL) {
			return -1;
		}

		len = scope->decl_ids_len;
		if (len > 0) {
			scope_id = scope->decl_ids[len-1];
			if (typealias_lists[scope_id] == NULL) {
				rc = list_init(&typealias_lists[scope_id]);
				if (rc != 0) {
					goto exit;
				}
			}
			/* As typealias_lists[scope_id] does not hold the
			 * ownership of its items (typealias_list_destroy does
			 * not free the list items), "key" does not need to be
			 * strdup'ed before it is inserted in the list.
			 */
			list_prepend(typealias_lists[scope_id], key);
		}
	}

	return 0;

exit:
	return rc;
}

static void typealias_list_destroy(void)
{
	uint32_t i;
	for (i = 0; i < typealias_lists_len; i++) {
		if (typealias_lists[i] != NULL) {
			list_destroy(&typealias_lists[i]);
		}
	}
	typealias_lists_len = 0;
	free(typealias_lists);
	typealias_lists = NULL;
}

static int typealias_list_create(struct policydb *pdb)
{
	uint32_t max_decl_id = 0;
	struct avrule_decl *decl;
	struct avrule_block *block;
	uint32_t rc = -1;

	for (block = pdb->global; block != NULL; block = block->next) {
		decl = block->branch_list;
		if (decl != NULL && decl->decl_id > max_decl_id) {
			max_decl_id = decl->decl_id;
		}
	}

	typealias_lists = calloc(max_decl_id + 1, sizeof(*typealias_lists));
	typealias_lists_len = max_decl_id + 1;

	rc = hashtab_map(pdb->p_types.table, typealiases_gather_map, pdb);
	if (rc != 0) {
		goto exit;
	}

	return 0;

exit:
	typealias_list_destroy();

	return rc;
}


static int stack_destroy(struct stack **stack)
{
	if (stack == NULL || *stack == NULL) {
		return 0;
	}

	free((*stack)->stack);
	free(*stack);
	*stack = NULL;

	return 0;
}

static int stack_init(struct stack **stack)
{
	int rc = -1;
	struct stack *s = calloc(1, sizeof(*s));
	if (s == NULL) {
		goto exit;
	}

	s->stack = mallocarray(STACK_SIZE, sizeof(*s->stack));
	if (s->stack == NULL) {
		goto exit;
	}

	s->pos = -1;
	s->size = STACK_SIZE;

	*stack = s;

	return 0;

exit:
	stack_destroy(&s);
	return rc;
}

static int stack_push(struct stack *stack, void *ptr)
{
	int rc = -1;
	void *new_stack;

	if (stack->pos + 1 == stack->size) {
		new_stack = reallocarray(stack->stack, stack->size * 2, sizeof(*stack->stack));
		if (new_stack == NULL) {
			goto exit;
		}
		stack->stack = new_stack;
		stack->size *= 2;
	}

	stack->pos++;
	stack->stack[stack->pos] = ptr;

	rc = 0;
exit:
	return rc;
}

static void *stack_pop(struct stack *stack)
{
	if (stack->pos == -1) {
		return NULL;
	}

	stack->pos--;
	return stack->stack[stack->pos + 1];
}

static void *stack_peek(struct stack *stack)
{
	if (stack->pos == -1) {
		return NULL;
	}

	return stack->stack[stack->pos];
}

static int is_id_in_scope_with_start(struct policydb *pdb, struct stack *decl_stack, int start, uint32_t symbol_type, char *id)
{
	int i;
	uint32_t j;
	struct avrule_decl *decl;
	struct scope_datum *scope;

	scope = hashtab_search(pdb->scope[symbol_type].table, id);
	if (scope == NULL) {
		return 0;
	}

	for (i = start; i >= 0; i--) {
		decl = decl_stack->stack[i];

		for (j = 0; j < scope->decl_ids_len; j++) {
			if (scope->decl_ids[j] == decl->decl_id) {
				return 1;
			}
		}
	}

	return 0;
}

static int is_id_in_ancestor_scope(struct policydb *pdb, struct stack *decl_stack, char *type, uint32_t symbol_type)
{
	int start = decl_stack->pos - 1;

	return is_id_in_scope_with_start(pdb, decl_stack, start, symbol_type, type);
}

static int is_id_in_scope(struct policydb *pdb, struct stack *decl_stack, char *type, uint32_t symbol_type)
{
	int start = decl_stack->pos;

	return is_id_in_scope_with_start(pdb, decl_stack, start, symbol_type, type);
}

static int semantic_level_to_cil(struct policydb *pdb, int sens_offset, struct mls_semantic_level *level)
{
	struct mls_semantic_cat *cat;

	cil_printf("(%s ", pdb->p_sens_val_to_name[level->sens - sens_offset]);

	if (level->cat != NULL) {
		cil_printf("(");
	}

	for (cat = level->cat; cat != NULL; cat = cat->next) {
		if (cat->low == cat->high) {
			cil_printf("%s", pdb->p_cat_val_to_name[cat->low - 1]);
		} else {
			cil_printf("range %s %s", pdb->p_cat_val_to_name[cat->low - 1], pdb->p_cat_val_to_name[cat->high - 1]);
		}

		if (cat->next != NULL) {
			cil_printf(" ");
		}
	}

	if (level->cat != NULL) {
		cil_printf(")");
	}

	cil_printf(")");

	return 0;
}

static int avrule_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const struct class_perm_node *classperms)
{
	int rc = -1;
	const char *rule;
	const struct class_perm_node *classperm;
	char *perms;

	switch (type) {
	case AVRULE_ALLOWED:
		rule = "allow";
		break;
	case AVRULE_AUDITALLOW:
		rule = "auditallow";
		break;
	case AVRULE_AUDITDENY:
		rule = "auditdeny";
		break;
	case AVRULE_DONTAUDIT:
		rule = "dontaudit";
		break;
	case AVRULE_NEVERALLOW:
		rule = "neverallow";
		break;
	case AVRULE_TRANSITION:
		rule = "typetransition";
		break;
	case AVRULE_MEMBER:
		rule = "typemember";
		break;
	case AVRULE_CHANGE:
		rule = "typechange";
		break;
	default:
		log_err("Unknown avrule type: %i", type);
		rc = -1;
		goto exit;
	}

	for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
		if (type & AVRULE_AV) {
			perms = sepol_av_to_string(pdb, classperm->tclass, classperm->data);
			if (perms == NULL) {
				log_err("Failed to generate permission string");
				rc = -1;
				goto exit;
			}
			cil_println(indent, "(%s %s %s (%s (%s)))",
					rule, src, tgt,
					pdb->p_class_val_to_name[classperm->tclass - 1],
					perms + 1);
		} else {
			cil_println(indent, "(%s %s %s %s %s)",
					rule, src, tgt,
					pdb->p_class_val_to_name[classperm->tclass - 1],
					pdb->p_type_val_to_name[classperm->data - 1]);
		}
	}

	return 0;

exit:
	return rc;
}

#define next_bit_in_range(i, p) ((i + 1 < sizeof(p)*8) && xperm_test((i + 1), p))

static int xperms_to_cil(const av_extended_perms_t *xperms)
{
	uint16_t value;
	uint16_t low_bit;
	uint16_t low_value;
	unsigned int bit;
	unsigned int in_range = 0;
	int first = 1;

	if ((xperms->specified != AVTAB_XPERMS_IOCTLFUNCTION)
		&& (xperms->specified != AVTAB_XPERMS_IOCTLDRIVER))
		return -1;

	for (bit = 0; bit < sizeof(xperms->perms)*8; bit++) {
		if (!xperm_test(bit, xperms->perms))
			continue;

		if (in_range && next_bit_in_range(bit, xperms->perms)) {
			/* continue until high value found */
			continue;
		} else if (next_bit_in_range(bit, xperms->perms)) {
			/* low value */
			low_bit = bit;
			in_range = 1;
			continue;
		}

		if (!first)
			cil_printf(" ");
		else
			first = 0;

		if (xperms->specified & AVTAB_XPERMS_IOCTLFUNCTION) {
			value = xperms->driver<<8 | bit;
			if (in_range) {
				low_value = xperms->driver<<8 | low_bit;
				cil_printf("(range 0x%hx 0x%hx)", low_value, value);
				in_range = 0;
			} else {
				cil_printf("0x%hx", value);
			}
		} else if (xperms->specified & AVTAB_XPERMS_IOCTLDRIVER) {
			value = bit << 8;
			if (in_range) {
				low_value = low_bit << 8;
				cil_printf("(range 0x%hx 0x%hx)", low_value, (uint16_t) (value|0xff));
				in_range = 0;
			} else {
				cil_printf("(range 0x%hx 0x%hx)", value, (uint16_t) (value|0xff));
			}
		}
	}

	return 0;
}

static int avrulex_to_cil(int indent, struct policydb *pdb, uint32_t type, const char *src, const char *tgt, const class_perm_node_t *classperms, const av_extended_perms_t *xperms)
{
	int rc = -1;
	const char *rule;
	const struct class_perm_node *classperm;

	switch (type) {
	case AVRULE_XPERMS_ALLOWED:
		rule = "allowx";
		break;
	case AVRULE_XPERMS_AUDITALLOW:
		rule = "auditallowx";
		break;
	case AVRULE_XPERMS_DONTAUDIT:
		rule = "dontauditx";
		break;
	case AVRULE_XPERMS_NEVERALLOW:
		rule = "neverallowx";
		break;
	default:
		log_err("Unknown avrule xperm type: %i", type);
		rc = -1;
		goto exit;
	}

	for (classperm = classperms; classperm != NULL; classperm = classperm->next) {
		cil_indent(indent);
		cil_printf("(%s %s %s (%s %s (", rule, src, tgt,
			   "ioctl", pdb->p_class_val_to_name[classperm->tclass - 1]);
		xperms_to_cil(xperms);
		cil_printf(")))\n");
	}

	return 0;

exit:
	return rc;
}

static unsigned int num_digits(unsigned int n)
{
	unsigned int num = 1;
	while (n >= 10) {
		n /= 10;
		num++;
	}
	return num;
}

static int ebitmap_to_cil(struct policydb *pdb, struct ebitmap *map, int type)
{
	struct ebitmap_node *node;
	uint32_t i;
	char **val_to_name = pdb->sym_val_to_name[type];

	ebitmap_for_each_positive_bit(map, node, i) {
		cil_printf("%s ", val_to_name[i]);
	}

	return 0;
}

static char *get_new_attr_name(struct policydb *pdb, int is_type)
{
	static unsigned int num_attrs = 0;
	int len, rlen;
	const char *infix;
	char *attr_name = NULL;

	num_attrs++;

	if (is_type) {
		infix = TYPEATTR_INFIX;
	} else {
		infix = ROLEATTR_INFIX;
	}

	len = strlen(pdb->name) + strlen(infix) + num_digits(num_attrs) + 1;
	attr_name = malloc(len);
	if (!attr_name) {
		log_err("Out of memory");
		goto exit;
	}

	rlen = snprintf(attr_name, len, "%s%s%i", pdb->name, infix, num_attrs);
	if (rlen < 0 || rlen >= len) {
		log_err("Failed to generate attribute name");
		free(attr_name);
		attr_name = NULL;
		goto exit;
	}

exit:
	return attr_name;
}

static int cil_add_attr_to_list(struct list *attr_list, char *attr_name, int is_type, void *set)
{
	struct attr_list_node *attr_list_node = NULL;
	int rc = 0;

	attr_list_node = calloc(1, sizeof(*attr_list_node));
	if (attr_list_node == NULL) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}

	rc = list_prepend(attr_list, attr_list_node);
	if (rc != 0) {
		goto exit;
	}

	attr_list_node->attr_name = attr_name;
	attr_list_node->is_type = is_type;
	attr_list_node->set = set;

	return rc;

exit:
	free(attr_list_node);
	return rc;
}

static int cil_print_attr_strs(int indent, struct policydb *pdb, int is_type, void *set, char *attr_name)
{
	// CIL doesn't support anonymous positive/negative/complemented sets.  So
	// instead we create a CIL type/roleattributeset that matches the set. If
	// the set has a negative set, then convert it to is (P & !N), where P is
	// the list of members in the positive set and N is the list of members
	// in the negative set. Additionally, if the set is complemented, then wrap
	// the whole thing with a negation.

	struct ebitmap_node *node;
	struct ebitmap *pos, *neg;
	uint32_t flags;
	unsigned i;
	struct type_set *ts;
	struct role_set *rs;
	int has_positive, has_negative;
	const char *kind;
	char **val_to_name;
	int rc = 0;

	if (is_type) {
		kind = "type";
		val_to_name = pdb->p_type_val_to_name;
		ts = (struct type_set *)set;
		pos = &ts->types;
		neg = &ts->negset;
		flags = ts->flags;
		has_positive = pos && !ebitmap_is_empty(pos);
		has_negative = neg && !ebitmap_is_empty(neg);
	} else {
		kind = "role";
		val_to_name = pdb->p_role_val_to_name;
		rs = (struct role_set *)set;
		pos = &rs->roles;
		neg = NULL;
		flags = rs->flags;
		has_positive = pos && !ebitmap_is_empty(pos);
		has_negative = 0;
	}

	cil_println(indent, "(%sattribute %s)", kind, attr_name);
	cil_indent(indent);
	cil_printf("(%sattributeset %s ", kind, attr_name);

	if (flags & TYPE_STAR) {
		cil_printf("(all)");
	}

	if (flags & TYPE_COMP) {
		cil_printf("(not ");
	}

	if (has_positive && has_negative) {
		cil_printf("(and ");
	}

	if (has_positive) {
		cil_printf("(");
		ebitmap_for_each_positive_bit(pos, node, i) {
			cil_printf("%s ", val_to_name[i]);
		}
		cil_printf(") ");
	}

	if (has_negative) {
		cil_printf("(not (");

		ebitmap_for_each_positive_bit(neg, node, i) {
			cil_printf("%s ", val_to_name[i]);
		}

		cil_printf("))");
	}

	if (has_positive && has_negative) {
		cil_printf(")");
	}

	if (flags & TYPE_COMP) {
		cil_printf(")");
	}

	cil_printf(")\n");

	return rc;
}

static int cil_print_attr_list(int indent, struct policydb *pdb, struct list *attr_list)
{
	struct list_node *curr;
	struct attr_list_node *node;
	int rc = 0;

	for (curr = attr_list->head; curr != NULL; curr = curr->next) {
		node = curr->data;
		rc = cil_print_attr_strs(indent, pdb, node->is_type, node->set, node->attr_name);
		if (rc != 0) {
			return rc;
		}
	}

	return rc;
}

static char *search_attr_list(struct list *attr_list, int is_type, void *set)
{
	struct list_node *curr;
	struct attr_list_node *node;
	struct role_set *rs1 = NULL, *rs2;
	struct type_set *ts1 = NULL, *ts2;

	if (is_type) {
		ts1 = (struct type_set *)set;
	} else {
		rs1 = (struct role_set *)set;
	}

	for (curr = attr_list->head; curr != NULL; curr = curr->next) {
		node = curr->data;
		if (node->is_type != is_type)
			continue;
		if (ts1) {
			ts2 = (struct type_set *)node->set;
			if (ts1->flags != ts2->flags)
				continue;
			if (ebitmap_cmp(&ts1->negset, &ts2->negset) == 0)
				continue;
			if (ebitmap_cmp(&ts1->types, &ts2->types) == 0)
				continue;
			return node->attr_name;
		} else {
			rs2 = (struct role_set *)node->set;
			if (rs1->flags != rs2->flags)
				continue;
			if (ebitmap_cmp(&rs1->roles, &rs2->roles) == 0)
				continue;
			return node->attr_name;
		}
	}

	return NULL;
}

static int set_to_names(struct policydb *pdb, int is_type, void *set, struct list *attr_list, char ***names, unsigned int *num_names)
{
	char *attr_name = NULL;
	int rc = 0;

	*names = NULL;
	*num_names = 0;

	attr_name = search_attr_list(attr_list, is_type, set);

	if (!attr_name) {
		attr_name = get_new_attr_name(pdb, is_type);
		if (!attr_name) {
			rc = -1;
			goto exit;
		}

		rc = cil_add_attr_to_list(attr_list, attr_name, is_type, set);
		if (rc != 0) {
			free(attr_name);
			goto exit;
		}
	}

	*names = malloc(sizeof(char *));
	if (!*names) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}
	*names[0] = attr_name;
	*num_names = 1;

exit:
	return rc;
}

static int ebitmap_to_names(struct ebitmap *map, char **vals_to_names, char ***names, unsigned int *num_names)
{
	int rc = 0;
	struct ebitmap_node *node;
	uint32_t i;
	unsigned int num;
	char **name_arr;

	num = 0;
	ebitmap_for_each_positive_bit(map, node, i) {
		if (num >= UINT32_MAX / sizeof(*name_arr)) {
			log_err("Overflow");
			rc = -1;
			goto exit;
		}
		num++;
	}

	if (!num) {
		*names = NULL;
		*num_names = 0;
		goto exit;
	}

	name_arr = mallocarray(num, sizeof(*name_arr));
	if (name_arr == NULL) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}

	num = 0;
	ebitmap_for_each_positive_bit(map, node, i) {
		name_arr[num] = vals_to_names[i];
		num++;
	}

	*names = name_arr;
	*num_names = num;

exit:
	return rc;
}

static int process_roleset(struct policydb *pdb, struct role_set *rs, struct list *attr_list, char ***names, unsigned int *num_names)
{
	int rc = 0;

	*names = NULL;
	*num_names = 0;

	if (rs->flags) {
		rc = set_to_names(pdb, 0, &rs->roles, attr_list, names, num_names);
		if (rc != 0) {
			goto exit;
		}
	} else {
		rc = ebitmap_to_names(&rs->roles, pdb->p_role_val_to_name, names, num_names);
		if (rc != 0) {
			goto exit;
		}
	}

exit:
	return rc;
}

static int process_typeset(struct policydb *pdb, struct type_set *ts, struct list *attr_list, char ***names, unsigned int *num_names)
{
	int rc = 0;

	*names = NULL;
	*num_names = 0;

	if (!ebitmap_is_empty(&ts->negset) || ts->flags != 0) {
		rc = set_to_names(pdb, 1, ts, attr_list, names, num_names);
		if (rc != 0) {
			goto exit;
		}
	} else {
		rc = ebitmap_to_names(&ts->types, pdb->p_type_val_to_name, names, num_names);
		if (rc != 0) {
			goto exit;
		}
	}

exit:
	return rc;
}

static void names_destroy(char ***names, unsigned int *num_names)
{
	free(*names);
	*names = NULL;
	*num_names = 0;
}

static int roletype_role_in_ancestor_to_cil(struct policydb *pdb, struct stack *decl_stack, char *type_name, int indent)
{
	struct list_node *curr;
	char **tnames = NULL;
	unsigned int num_tnames, i;
	struct role_list_node *role_node = NULL;
	int rc;
	struct type_set *ts;
	struct list *attr_list = NULL;

	rc = list_init(&attr_list);
	if (rc != 0) {
		goto exit;
	}

	for (curr = role_list->head; curr != NULL; curr = curr->next) {
		role_node = curr->data;
		if (!is_id_in_ancestor_scope(pdb, decl_stack, role_node->role_name, SYM_ROLES)) {
			continue;
		}

		ts = &role_node->role->types;
		rc = process_typeset(pdb, ts, attr_list, &tnames, &num_tnames);
		if (rc != 0) {
			goto exit;
		}
		for (i = 0; i < num_tnames; i++) {
			if (!strcmp(type_name, tnames[i])) {
				cil_println(indent, "(roletype %s %s)", role_node->role_name, type_name);
			}
		}
		names_destroy(&tnames, &num_tnames);
	}

	rc = cil_print_attr_list(indent, pdb, attr_list);
	if (rc != 0) {
		goto exit;
	}

exit:
	attr_list_destroy(&attr_list);
	return rc;
}


static int name_list_to_string(char **names, unsigned int num_names, char **string)
{
	// create a space separated string of the names
	int rc = -1;
	size_t len = 0;
	unsigned int i;
	char *str;
	char *strpos;

	for (i = 0; i < num_names; i++) {
		if (__builtin_add_overflow(len, strlen(names[i]), &len)) {
			log_err("Overflow");
			return -1;
		}
	}

	// add spaces + null terminator
	if (__builtin_add_overflow(len, (size_t)num_names, &len)) {
		log_err("Overflow");
		return -1;
	}

	if (!len) {
		log_err("Empty list");
		return -1;
	}

	str = malloc(len);
	if (str == NULL) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}
	str[0] = 0;

	strpos = str;

	for (i = 0; i < num_names; i++) {
		strpos = stpcpy(strpos, names[i]);
		if (i < num_names - 1) {
			*strpos++ = ' ';
		}
	}

	*string = str;

	return 0;
exit:
	free(str);
	return rc;
}

static int avrule_list_to_cil(int indent, struct policydb *pdb, struct avrule *avrule_list, struct list *attr_list)
{
	int rc = -1;
	struct avrule *avrule;
	char **snames = NULL;
	char **tnames = NULL;
	unsigned int s, t, num_snames, num_tnames;
	struct type_set *ts;

	for (avrule = avrule_list; avrule != NULL; avrule = avrule->next) {
		if ((avrule->specified & (AVRULE_NEVERALLOW|AVRULE_XPERMS_NEVERALLOW)) &&
		    avrule->source_filename) {
			cil_println(0, ";;* lmx %lu %s\n",avrule->source_line, avrule->source_filename);
		}

		ts = &avrule->stypes;
		rc = process_typeset(pdb, ts, attr_list, &snames, &num_snames);
		if (rc != 0) {
			goto exit;
		}

		ts = &avrule->ttypes;
		rc = process_typeset(pdb, ts, attr_list, &tnames, &num_tnames);
		if (rc != 0) {
			goto exit;
		}

		for (s = 0; s < num_snames; s++) {
			for (t = 0; t < num_tnames; t++) {
				if (avrule->specified & AVRULE_XPERMS) {
					rc = avrulex_to_cil(indent, pdb, avrule->specified, snames[s], tnames[t], avrule->perms, avrule->xperms);
				} else {
					rc = avrule_to_cil(indent, pdb, avrule->specified, snames[s], tnames[t], avrule->perms);
				}
				if (rc != 0) {
					goto exit;
				}
			}

			if (avrule->flags & RULE_SELF) {
				if (avrule->specified & AVRULE_XPERMS) {
					rc = avrulex_to_cil(indent, pdb, avrule->specified, snames[s], "self", avrule->perms, avrule->xperms);
				} else {
					rc = avrule_to_cil(indent, pdb, avrule->specified, snames[s], "self", avrule->perms);
				}
				if (rc != 0) {
					goto exit;
				}
			}
		}

		names_destroy(&snames, &num_snames);
		names_destroy(&tnames, &num_tnames);

		if ((avrule->specified & (AVRULE_NEVERALLOW|AVRULE_XPERMS_NEVERALLOW)) &&
		    avrule->source_filename) {
			cil_println(0, ";;* lme\n");
		}
	}

	return 0;

exit:
	names_destroy(&snames, &num_snames);
	names_destroy(&tnames, &num_tnames);

	return rc;
}

static int cond_expr_to_cil(int indent, struct policydb *pdb, struct cond_expr *cond_expr, uint32_t flags)
{
	int rc = 0;
	struct cond_expr *curr;
	struct stack *stack = NULL;
	int len = 0;
	int rlen;
	char *new_val = NULL;
	char *val1 = NULL;
	char *val2 = NULL;
	unsigned int num_params;
	const char *op;
	const char *sep;
	const char *type;

	rc = stack_init(&stack);
	if (rc != 0) {
		log_err("Out of memory");
		goto exit;
	}

	for (curr = cond_expr; curr != NULL; curr = curr->next) {
		if (curr->expr_type == COND_BOOL) {
			val1 = pdb->p_bool_val_to_name[curr->bool - 1];
			// length of boolean + 2 parens + null terminator
			len = strlen(val1) + 2 + 1;
			new_val = malloc(len);
			if (new_val == NULL) {
				log_err("Out of memory");
				rc = -1;
				goto exit;
			}
			rlen = snprintf(new_val, len, "(%s)", val1);
			if (rlen < 0 || rlen >= len) {
				log_err("Failed to generate conditional expression");
				rc = -1;
				goto exit;
			}
		} else {
			switch(curr->expr_type) {
			case COND_NOT:	op = "not";	break;
			case COND_OR:	op = "or";	break;
			case COND_AND:	op = "and";	break;
			case COND_XOR:	op = "xor";	break;
			case COND_EQ:	op = "eq";	break;
			case COND_NEQ:	op = "neq";	break;
			default:
				rc = -1;
				goto exit;
			}

			num_params = curr->expr_type == COND_NOT ? 1 : 2;

			if (num_params == 1) {
				val1 = stack_pop(stack);
				val2 = strdup("");
				if (val2 == NULL) {
					log_err("Out of memory");
					rc = -1;
					goto exit;
				}
				sep = "";
			} else {
				val2 = stack_pop(stack);
				val1 = stack_pop(stack);
				sep = " ";
			}

			if (val1 == NULL || val2 == NULL) {
				log_err("Invalid conditional expression");
				rc = -1;
				goto exit;
			}

			// length = length of parameters +
			//          length of operator +
			//          1 space preceding each parameter +
			//          2 parens around the whole expression
			//          + null terminator
			len = strlen(val1) + strlen(val2) + strlen(op) + (num_params * 1) + 2 + 1;
			new_val = malloc(len);
			if (new_val == NULL) {
				log_err("Out of memory");
				rc = -1;
				goto exit;
			}

			rlen = snprintf(new_val, len, "(%s %s%s%s)", op, val1, sep, val2);
			if (rlen < 0 || rlen >= len) {
				log_err("Failed to generate conditional expression");
				rc = -1;
				goto exit;
			}

			free(val1);
			free(val2);
			val1 = NULL;
			val2 = NULL;
		}

		rc = stack_push(stack, new_val);
		if (rc != 0) {
			log_err("Out of memory");
			goto exit;
		}
		new_val = NULL;
	}

	if (flags & COND_NODE_FLAGS_TUNABLE) {
		type = "tunableif";
	} else {
		type = "booleanif";
	}

	val1 = stack_pop(stack);
	if (val1 == NULL || stack_peek(stack) != NULL) {
		log_err("Invalid conditional expression");
		rc = -1;
		goto exit;
	}

	cil_println(indent, "(%s %s", type, val1);
	free(val1);
	val1 = NULL;

	rc = 0;

exit:
	free(new_val);
	free(val1);
	free(val2);
	if (stack != NULL) {
		while ((val1 = stack_pop(stack)) != NULL) {
			free(val1);
		}
		stack_destroy(&stack);
	}
	return rc;
}

static int cond_list_to_cil(int indent, struct policydb *pdb, struct cond_node *cond_list, struct list *attr_list)
{
	int rc = 0;
	struct cond_node *cond;

	for (cond = cond_list; cond != NULL; cond = cond->next) {

		rc = cond_expr_to_cil(indent, pdb, cond->expr, cond->flags);
		if (rc != 0) {
			goto exit;
		}

		if (cond->avtrue_list != NULL) {
			cil_println(indent + 1, "(true");
			rc = avrule_list_to_cil(indent + 2, pdb, cond->avtrue_list, attr_list);
			if (rc != 0) {
				goto exit;
			}
			cil_println(indent + 1, ")");
		}

		if (cond->avfalse_list != NULL) {
			cil_println(indent + 1, "(false");
			rc = avrule_list_to_cil(indent + 2, pdb, cond->avfalse_list, attr_list);
			if (rc != 0) {
				goto exit;
			}
			cil_println(indent + 1, ")");
		}

		cil_println(indent, ")");
	}

exit:
	return rc;
}

static int role_trans_to_cil(int indent, struct policydb *pdb, struct role_trans_rule *rules, struct list *role_attr_list, struct list *type_attr_list)
{
	int rc = 0;
	struct role_trans_rule *rule;
	char **role_names = NULL;
	unsigned int num_role_names = 0;
	unsigned int role;
	char **type_names = NULL;
	unsigned int num_type_names = 0;
	unsigned int type;
	uint32_t i;
	struct ebitmap_node *node;
	struct type_set *ts;
	struct role_set *rs;

	for (rule = rules; rule != NULL; rule = rule->next) {
		rs = &rule->roles;
		rc = process_roleset(pdb, rs, role_attr_list, &role_names, &num_role_names);
		if (rc != 0) {
			goto exit;
		}

		ts = &rule->types;
		rc = process_typeset(pdb, ts, type_attr_list, &type_names, &num_type_names);
		if (rc != 0) {
			goto exit;
		}

		for (role = 0; role < num_role_names; role++) {
			for (type = 0; type < num_type_names; type++) {
				ebitmap_for_each_positive_bit(&rule->classes, node, i) {
					cil_println(indent, "(roletransition %s %s %s %s)",
						    role_names[role], type_names[type],
						    pdb->p_class_val_to_name[i],
						    pdb->p_role_val_to_name[rule->new_role - 1]);
				}
			}
		}

		names_destroy(&role_names, &num_role_names);
		names_destroy(&type_names, &num_type_names);
	}

exit:
	names_destroy(&role_names, &num_role_names);
	names_destroy(&type_names, &num_type_names);

	return rc;
}

static int role_allows_to_cil(int indent, struct policydb *pdb, struct role_allow_rule *rules, struct list *attr_list)
{
	int rc = -1;
	struct role_allow_rule *rule;
	char **roles = NULL;
	unsigned int num_roles = 0;
	char **new_roles = NULL;
	unsigned int num_new_roles = 0;
	unsigned int i, j;
	struct role_set *rs;

	for (rule = rules; rule != NULL; rule = rule->next) {
		rs = &rule->roles;
		rc = process_roleset(pdb, rs, attr_list, &roles, &num_roles);
		if (rc != 0) {
			goto exit;
		}

		rs = &rule->new_roles;
		rc = process_roleset(pdb, rs, attr_list, &new_roles, &num_new_roles);
		if (rc != 0) {
			goto exit;
		}

		for (i = 0; i < num_roles; i++) {
			for (j = 0; j < num_new_roles; j++) {
				cil_println(indent, "(roleallow %s %s)", roles[i], new_roles[j]);
			}
		}

		names_destroy(&roles, &num_roles);
		names_destroy(&new_roles, &num_new_roles);
	}

	rc = 0;

exit:
	names_destroy(&roles, &num_roles);
	names_destroy(&new_roles, &num_new_roles);

	return rc;
}

static int range_trans_to_cil(int indent, struct policydb *pdb, struct range_trans_rule *rules, struct list *attr_list)
{
	int rc = -1;
	struct range_trans_rule *rule;
	char **stypes = NULL;
	unsigned int num_stypes = 0;
	unsigned int stype;
	char **ttypes = NULL;
	unsigned int num_ttypes = 0;
	unsigned int ttype;
	struct ebitmap_node *node;
	uint32_t i;
	struct type_set *ts;

	if (!pdb->mls) {
		return 0;
	}

	for (rule = rules; rule != NULL; rule = rule->next) {
		ts = &rule->stypes;
		rc = process_typeset(pdb, ts, attr_list, &stypes, &num_stypes);
		if (rc != 0) {
			goto exit;
		}

		ts = &rule->ttypes;
		rc = process_typeset(pdb, ts, attr_list, &ttypes, &num_ttypes);
		if (rc != 0) {
			goto exit;
		}

		for (stype = 0; stype < num_stypes; stype++) {
			for (ttype = 0; ttype < num_ttypes; ttype++) {
				ebitmap_for_each_positive_bit(&rule->tclasses, node, i) {
					cil_indent(indent);
					cil_printf("(rangetransition %s %s %s ", stypes[stype], ttypes[ttype], pdb->p_class_val_to_name[i]);

					cil_printf("(");

					rc = semantic_level_to_cil(pdb, 1, &rule->trange.level[0]);
					if (rc != 0) {
						goto exit;
					}

					cil_printf(" ");

					rc = semantic_level_to_cil(pdb, 1, &rule->trange.level[1]);
					if (rc != 0) {
						goto exit;
					}

					cil_printf("))\n");
				}

			}
		}

		names_destroy(&stypes, &num_stypes);
		names_destroy(&ttypes, &num_ttypes);
	}

	rc = 0;

exit:
	names_destroy(&stypes, &num_stypes);
	names_destroy(&ttypes, &num_ttypes);

	return rc;
}

static int filename_trans_to_cil(int indent, struct policydb *pdb, struct filename_trans_rule *rules, struct list *attr_list)
{
	int rc = -1;
	char **stypes = NULL;
	unsigned int num_stypes = 0;
	unsigned int stype;
	char **ttypes = NULL;
	unsigned int num_ttypes = 0;
	unsigned int ttype;
	struct type_set *ts;
	struct filename_trans_rule *rule;

	for (rule = rules; rule != NULL; rule = rule->next) {
		ts = &rule->stypes;
		rc = process_typeset(pdb, ts, attr_list, &stypes, &num_stypes);
		if (rc != 0) {
			goto exit;
		}

		ts = &rule->ttypes;
		rc = process_typeset(pdb, ts, attr_list, &ttypes, &num_ttypes);
		if (rc != 0) {
			goto exit;
		}

		for (stype = 0; stype < num_stypes; stype++) {
			for (ttype = 0; ttype < num_ttypes; ttype++) {
				cil_println(indent, "(typetransition %s %s %s \"%s\" %s)",
					    stypes[stype], ttypes[ttype],
					    pdb->p_class_val_to_name[rule->tclass - 1],
					    rule->name,
					    pdb->p_type_val_to_name[rule->otype - 1]);
			}
		}

		names_destroy(&stypes, &num_stypes);
		names_destroy(&ttypes, &num_ttypes);
	}

	rc = 0;
exit:
	names_destroy(&stypes, &num_stypes);
	names_destroy(&ttypes, &num_ttypes);

	return rc;
}

struct class_perm_datum {
	char *name;
	uint32_t val;
};

struct class_perm_array {
	struct class_perm_datum *perms;
	uint32_t count;
};

static int class_perm_to_array(char *key, void *data, void *args)
{
	struct class_perm_array *arr = args;
	struct perm_datum *datum = data;
	arr->perms[arr->count].name = key;
	arr->perms[arr->count].val = datum->s.value;
	arr->count++;

	return 0;
}

static int class_perm_cmp(const void *a, const void *b)
{
	const struct class_perm_datum *aa = a;
	const struct class_perm_datum *bb = b;

	return aa->val - bb->val;
}

static int common_to_cil(char *key, void *data, void *UNUSED(arg))
{
	int rc = -1;
	struct common_datum *common = data;
	struct class_perm_array arr;
	uint32_t i;

	arr.count = 0;
	arr.perms = calloc(common->permissions.nprim, sizeof(*arr.perms));
	if (arr.perms == NULL) {
		goto exit;
	}
	rc = hashtab_map(common->permissions.table, class_perm_to_array, &arr);
	if (rc != 0) {
		goto exit;
	}

	qsort(arr.perms, arr.count, sizeof(*arr.perms), class_perm_cmp);

	cil_printf("(common %s (", key);
	for (i = 0; i < arr.count; i++) {
		cil_printf("%s ", arr.perms[i].name);
	}
	cil_printf("))\n");

	rc = 0;

exit:
	free(arr.perms);
	return rc;
}


static int constraint_expr_to_string(struct policydb *pdb, struct constraint_expr *exprs, char **expr_string)
{
	int rc = -1;
	struct constraint_expr *expr;
	struct stack *stack = NULL;
	int len = 0;
	int rlen;
	char *new_val = NULL;
	char *val1 = NULL;
	char *val2 = NULL;
	uint32_t num_params;
	const char *op;
	const char *sep;
	const char *attr1;
	const char *attr2;
	char *names = NULL;
	char **name_list = NULL;
	unsigned int num_names = 0;
	struct type_set *ts;

	rc = stack_init(&stack);
	if (rc != 0) {
		goto exit;
	}

	for (expr = exprs; expr != NULL; expr = expr->next) {
		if (expr->expr_type == CEXPR_ATTR || expr->expr_type == CEXPR_NAMES) {
			switch (expr->op) {
			case CEXPR_EQ:      op = "eq";     break;
			case CEXPR_NEQ:     op = "neq";    break;
			case CEXPR_DOM:     op = "dom";    break;
			case CEXPR_DOMBY:   op = "domby";  break;
			case CEXPR_INCOMP:  op = "incomp"; break;
			default:
				log_err("Unknown constraint operator type: %i", expr->op);
				rc = -1;
				goto exit;
			}

			switch (expr->attr) {
			case CEXPR_USER:                 attr1 = "u1"; attr2 = "u2"; break;
			case CEXPR_USER | CEXPR_TARGET:  attr1 = "u2"; attr2 = "";   break;
			case CEXPR_USER | CEXPR_XTARGET: attr1 = "u3"; attr2 = "";   break;
			case CEXPR_ROLE:                 attr1 = "r1"; attr2 = "r2"; break;
			case CEXPR_ROLE | CEXPR_TARGET:  attr1 = "r2"; attr2 = "";   break;
			case CEXPR_ROLE | CEXPR_XTARGET: attr1 = "r3"; attr2 = "";   break;
			case CEXPR_TYPE:                 attr1 = "t1"; attr2 = "t2"; break;
			case CEXPR_TYPE | CEXPR_TARGET:  attr1 = "t2"; attr2 = "";   break;
			case CEXPR_TYPE | CEXPR_XTARGET: attr1 = "t3"; attr2 = "";   break;
			case CEXPR_L1L2:                 attr1 = "l1"; attr2 = "l2"; break;
			case CEXPR_L1H2:                 attr1 = "l1"; attr2 = "h2"; break;
			case CEXPR_H1L2:                 attr1 = "h1"; attr2 = "l2"; break;
			case CEXPR_H1H2:                 attr1 = "h1"; attr2 = "h2"; break;
			case CEXPR_L1H1:                 attr1 = "l1"; attr2 = "h1"; break;
			case CEXPR_L2H2:                 attr1 = "l2"; attr2 = "h2"; break;
			default:
				log_err("Unknown expression attribute type: %i", expr->attr);
				rc = -1;
				goto exit;
			}

			if (expr->expr_type == CEXPR_ATTR) {
				// length of values/attrs + 2 separating spaces + 2 parens + null terminator
				len = strlen(op) + strlen(attr1) + strlen(attr2) + 2 + 2 + 1;
				new_val = malloc(len);
				if (new_val == NULL) {
					log_err("Out of memory");
					rc = -1;
					goto exit;
				}
				rlen = snprintf(new_val, len, "(%s %s %s)", op, attr1, attr2);
				if (rlen < 0 || rlen >= len) {
					log_err("Failed to generate constraint expression");
					rc = -1;
					goto exit;
				}
			} else {
				if (expr->attr & CEXPR_TYPE) {
					ts = expr->type_names;
					rc = ebitmap_to_names(&ts->types, pdb->p_type_val_to_name, &name_list, &num_names);
					if (rc != 0) {
						goto exit;
					}
				} else if (expr->attr & CEXPR_USER) {
					rc = ebitmap_to_names(&expr->names, pdb->p_user_val_to_name, &name_list, &num_names);
					if (rc != 0) {
						goto exit;
					}
				} else if (expr->attr & CEXPR_ROLE) {
					rc = ebitmap_to_names(&expr->names, pdb->p_role_val_to_name, &name_list, &num_names);
					if (rc != 0) {
						goto exit;
					}
				}
				if (num_names == 0) {
					names = strdup("NO_IDENTIFIER");
				} else {
					rc = name_list_to_string(name_list, num_names, &names);
					if (rc != 0) {
						goto exit;
					}
				}

				// length of values/oper + 2 spaces + 2 parens + null terminator
				len = strlen(op) + strlen(attr1) +  strlen(names) + 2 + 2 + 1;
				if (num_names > 1) {
					len += 2; // 2 more parens
				}
				new_val = malloc(len);
				if (new_val == NULL) {
					log_err("Out of memory");
					rc = -1;
					goto exit;
				}
				if (num_names > 1) {
					rlen = snprintf(new_val, len, "(%s %s (%s))", op, attr1, names);
				} else {
					rlen = snprintf(new_val, len, "(%s %s %s)", op, attr1, names);
				}
				if (rlen < 0 || rlen >= len) {
					log_err("Failed to generate constraint expression");
					rc = -1;
					goto exit;
				}

				names_destroy(&name_list, &num_names);
				free(names);
				names = NULL;
			}
		} else {
			switch (expr->expr_type) {
			case CEXPR_NOT: op = "not"; break;
			case CEXPR_AND: op = "and"; break;
			case CEXPR_OR:  op = "or"; break;
			default:
				log_err("Unknown constraint expression type: %i", expr->expr_type);
				rc = -1;
				goto exit;
			}

			num_params = expr->expr_type == CEXPR_NOT ? 1 : 2;

			if (num_params == 1) {
				val1 = stack_pop(stack);
				val2 = strdup("");
				if (val2 == NULL) {
					log_err("Out of memory");
					rc = -1;
					goto exit;
				}
				sep = "";
			} else {
				val2 = stack_pop(stack);
				val1 = stack_pop(stack);
				sep = " ";
			}

			if (val1 == NULL || val2 == NULL) {
				log_err("Invalid constraint expression");
				rc = -1;
				goto exit;
			}

			// length = length of parameters +
			//          length of operator +
			//          1 space preceding each parameter +
			//          2 parens around the whole expression
			//          + null terminator
			len = strlen(val1) + strlen(val2) + strlen(op) + (num_params * 1) + 2 + 1;
			new_val = malloc(len);
			if (new_val == NULL) {
				log_err("Out of memory");
				rc = -1;
				goto exit;
			}

			rlen = snprintf(new_val, len, "(%s %s%s%s)", op, val1, sep, val2);
			if (rlen < 0 || rlen >= len) {
				log_err("Failed to generate constraint expression");
				rc = -1;
				goto exit;
			}

			free(val1);
			free(val2);
			val1 = NULL;
			val2 = NULL;
		}

		rc = stack_push(stack, new_val);
		if (rc != 0) {
			log_err("Out of memory");
			goto exit;
		}

		new_val = NULL;
	}

	new_val = stack_pop(stack);
	if (new_val == NULL || stack_peek(stack) != NULL) {
		log_err("Invalid constraint expression");
		rc = -1;
		goto exit;
	}

	*expr_string = new_val;
	new_val = NULL;

	rc = 0;

exit:
	names_destroy(&name_list, &num_names);
	free(names);

	free(new_val);
	free(val1);
	free(val2);
	if (stack != NULL) {
		while ((val1 = stack_pop(stack)) != NULL) {
			free(val1);
		}
		stack_destroy(&stack);
	}

	return rc;
}


static int constraints_to_cil(int indent, struct policydb *pdb, char *classkey, struct class_datum *class, struct constraint_node *constraints, int is_constraint)
{
	int rc = -1;
	struct constraint_node *node;
	char *expr = NULL;
	const char *mls;
	char *perms;

	mls = pdb->mls ? "mls" : "";

	for (node = constraints; node != NULL; node = node->next) {

		rc = constraint_expr_to_string(pdb, node->expr, &expr);
		if (rc != 0) {
			goto exit;
		}

		if (is_constraint) {
			perms = sepol_av_to_string(pdb, class->s.value, node->permissions);
			cil_println(indent, "(%sconstrain (%s (%s)) %s)", mls, classkey, perms + 1, expr);
		} else {
			cil_println(indent, "(%svalidatetrans %s %s)", mls, classkey, expr);
		}

		free(expr);
		expr = NULL;
	}

	rc = 0;

exit:
	free(expr);
	return rc;
}

static int class_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *UNUSED(decl_stack), char *key, void *datum, int scope)
{
	int rc = -1;
	struct class_datum *class = datum;
	const char *dflt;
	struct class_perm_array arr;
	uint32_t i;

	if (scope == SCOPE_REQ) {
		return 0;
	}

	arr.count = 0;
	arr.perms = calloc(class->permissions.nprim, sizeof(*arr.perms));
	if (arr.perms == NULL) {
		goto exit;
	}
	rc = hashtab_map(class->permissions.table, class_perm_to_array, &arr);
	if (rc != 0) {
		goto exit;
	}

	qsort(arr.perms, arr.count, sizeof(*arr.perms), class_perm_cmp);

	cil_indent(indent);
	cil_printf("(class %s (", key);
	for (i = 0; i < arr.count; i++) {
		cil_printf("%s ", arr.perms[i].name);
	}
	cil_printf("))\n");

	if (class->comkey != NULL) {
		cil_println(indent, "(classcommon %s %s)", key, class->comkey);
	}

	if (class->default_user != 0) {
		switch (class->default_user) {
		case DEFAULT_SOURCE:	dflt = "source";	break;
		case DEFAULT_TARGET:	dflt = "target";	break;
		default:
			log_err("Unknown default user value: %i", class->default_user);
			rc = -1;
			goto exit;
		}
		cil_println(indent, "(defaultuser %s %s)", key, dflt);
	}

	if (class->default_role != 0) {
		switch (class->default_role) {
		case DEFAULT_SOURCE:	dflt = "source";	break;
		case DEFAULT_TARGET:	dflt = "target";	break;
		default:
			log_err("Unknown default role value: %i", class->default_role);
			rc = -1;
			goto exit;
		}
		cil_println(indent, "(defaultrole %s %s)", key, dflt);
	}

	if (class->default_type != 0) {
		switch (class->default_type) {
		case DEFAULT_SOURCE:	dflt = "source";	break;
		case DEFAULT_TARGET:	dflt = "target";	break;
		default:
			log_err("Unknown default type value: %i", class->default_type);
			rc = -1;
			goto exit;
		}
		cil_println(indent, "(defaulttype %s %s)", key, dflt);
	}

	if (class->default_range != 0) {
		switch (class->default_range) {
		case DEFAULT_SOURCE_LOW:		dflt = "source low";	break;
		case DEFAULT_SOURCE_HIGH:		dflt = "source high";	break;
		case DEFAULT_SOURCE_LOW_HIGH:	dflt = "source low-high";	break;
		case DEFAULT_TARGET_LOW:		dflt = "target low";	break;
		case DEFAULT_TARGET_HIGH:		dflt = "target high";	break;
		case DEFAULT_TARGET_LOW_HIGH:	dflt = "target low-high";	break;
		case DEFAULT_GLBLUB:		dflt = "glblub";		break;
		default:
			log_err("Unknown default range value: %i", class->default_range);
			rc = -1;
			goto exit;
		}
		cil_println(indent, "(defaultrange %s %s)", key, dflt);

	}

	if (class->constraints != NULL) {
		rc = constraints_to_cil(indent, pdb, key, class, class->constraints, 1);
		if (rc != 0) {
			goto exit;
		}
	}

	if (class->validatetrans != NULL) {
		rc = constraints_to_cil(indent, pdb, key, class, class->validatetrans, 0);
		if (rc != 0) {
			goto exit;
		}
	}

	rc = 0;

exit:
	free(arr.perms);
	return rc;
}

static int class_order_to_cil(int indent, struct policydb *pdb, struct ebitmap order)
{
	struct ebitmap_node *node;
	uint32_t i;

	if (ebitmap_is_empty(&order)) {
		return 0;
	}

	cil_indent(indent);
	cil_printf("(classorder (");

	ebitmap_for_each_positive_bit(&order, node, i) {
		cil_printf("%s ", pdb->sym_val_to_name[SYM_CLASSES][i]);
	}

	cil_printf("))\n");

	return 0;
}

static int role_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *decl_stack, char *key, void *datum, int scope)
{
	int rc = -1;
	struct ebitmap_node *node;
	uint32_t i;
	unsigned int j;
	char **types = NULL;
	unsigned int num_types = 0;
	struct role_datum *role = datum;
	struct type_set *ts;
	struct list *attr_list = NULL;

	rc = list_init(&attr_list);
	if (rc != 0) {
		goto exit;
	}

	if (scope == SCOPE_REQ) {
		// if a role/roleattr is in the REQ scope, then it could cause an
		// optional block to fail, even if it is never used. However in CIL,
		// symbols must be used in order to cause an optional block to fail. So
		// for symbols in the REQ scope, add them to a roleattribute as a way
		// to 'use' them in the optional without affecting the resulting policy.
		cil_println(indent, "(roleattributeset " GEN_REQUIRE_ATTR " %s)", key);
	}

	switch (role->flavor) {
	case ROLE_ROLE:
		if (scope == SCOPE_DECL) {
			// Only declare certain roles if we are reading a base module.
			// These roles are defined in the base module and sometimes in
			// other non-base modules. If we generated the roles regardless of
			// the policy type, it would result in duplicate declarations,
			// which isn't allowed in CIL. Patches have been made to refpolicy
			// to remove these duplicate role declarations, but we need to be
			// backwards compatible and support older policies. Since we know
			// these roles are always declared in base, only print them when we
			// see them in the base module. If the declarations appear in a
			// non-base module, ignore their declarations.
			//
			// Note that this is a hack, and if a policy author does not define
			// one of these roles in base, the declaration will not appear in
			// the resulting policy, likely resulting in a compilation error in
			// CIL.
			//
			// To make things more complicated, the auditadm_r and secadm_r
			// roles could actually be in either the base module or a non-base
			// module, or both. So we can't rely on this same behavior. So for
			// these roles, don't declare them here, even if they are in a base
			// or non-base module. Instead we will just declare them in the
			// base module elsewhere.
			int is_base_role = (!strcmp(key, "user_r") ||
			                    !strcmp(key, "staff_r") ||
			                    !strcmp(key, "sysadm_r") ||
			                    !strcmp(key, "system_r") ||
			                    !strcmp(key, "unconfined_r"));
			int is_builtin_role = (!strcmp(key, "auditadm_r") ||
			                       !strcmp(key, "secadm_r"));
			if ((is_base_role && pdb->policy_type == SEPOL_POLICY_BASE) ||
			    (!is_base_role && !is_builtin_role)) {
				cil_println(indent, "(role %s)", key);
			}
		}

		if (ebitmap_cardinality(&role->dominates) > 1) {
			log_err("Warning: role 'dominance' statement unsupported in CIL. Dropping from output.");
		}

		ts = &role->types;
		rc = process_typeset(pdb, ts, attr_list, &types, &num_types);
		if (rc != 0) {
			goto exit;
		}

		for (j = 0; j < num_types; j++) {
			if (is_id_in_scope(pdb, decl_stack, types[j], SYM_TYPES)) {
				cil_println(indent, "(roletype %s %s)", key, types[j]);
			}
		}

		if (role->bounds > 0) {
			cil_println(indent, "(rolebounds %s %s)", key, pdb->p_role_val_to_name[role->bounds - 1]);
		}
		break;

	case ROLE_ATTRIB:
		if (scope == SCOPE_DECL) {
			cil_println(indent, "(roleattribute %s)", key);
		}

		if (!ebitmap_is_empty(&role->roles)) {
			cil_indent(indent);
			cil_printf("(roleattributeset %s (", key);
			ebitmap_for_each_positive_bit(&role->roles, node, i) {
				cil_printf("%s ", pdb->p_role_val_to_name[i]);
			}
			cil_printf("))\n");
		}

		ts = &role->types;
		rc = process_typeset(pdb, ts, attr_list, &types, &num_types);
		if (rc != 0) {
			goto exit;
		}


		for (j = 0; j < num_types; j++) {
			if (is_id_in_scope(pdb, decl_stack, types[j], SYM_TYPES)) {
				cil_println(indent, "(roletype %s %s)", key, types[j]);
			}
		}

		break;

	default:
		log_err("Unknown role type: %i", role->flavor);
		rc = -1;
		goto exit;
	}

	rc = cil_print_attr_list(indent, pdb, attr_list);
	if (rc != 0) {
		goto exit;
	}

exit:
	attr_list_destroy(&attr_list);
	names_destroy(&types, &num_types);

	return rc;
}

static int type_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *decl_stack, char *key, void *datum, int scope)
{
	int rc = -1;
	struct type_datum *type = datum;

	if (scope == SCOPE_REQ) {
		// if a type/typeattr is in the REQ scope, then it could cause an
		// optional block to fail, even if it is never used. However in CIL,
		// symbols must be used in order to cause an optional block to fail. So
		// for symbols in the REQ scope, add them to a typeattribute as a way
		// to 'use' them in the optional without affecting the resulting policy.
		cil_println(indent, "(typeattributeset " GEN_REQUIRE_ATTR " %s)", key);
	}

	rc = roletype_role_in_ancestor_to_cil(pdb, decl_stack, key, indent);
	if (rc != 0) {
		goto exit;
	}

	switch(type->flavor) {
	case TYPE_TYPE:
		if (scope == SCOPE_DECL) {
			cil_println(indent, "(type %s)", key);
			// object_r is implicit in checkmodule, but not with CIL,
			// create it as part of base
			cil_println(indent, "(roletype " DEFAULT_OBJECT " %s)", key);
		}

		if (type->flags & TYPE_FLAGS_PERMISSIVE) {
			cil_println(indent, "(typepermissive %s)", key);
		}

		if (type->bounds > 0) {
			cil_println(indent, "(typebounds %s %s)", pdb->p_type_val_to_name[type->bounds - 1], key);
		}
		break;
	case TYPE_ATTRIB:
		if (scope == SCOPE_DECL) {
			cil_println(indent, "(typeattribute %s)", key);
		}

		if (type->flags & TYPE_FLAGS_EXPAND_ATTR) {
			cil_indent(indent);
			cil_printf("(expandtypeattribute (%s) ", key);
			if (type->flags & TYPE_FLAGS_EXPAND_ATTR_TRUE) {
				cil_printf("true");
			} else if (type->flags & TYPE_FLAGS_EXPAND_ATTR_FALSE) {
				cil_printf("false");
			}
			cil_printf(")\n");
		}

		if (!ebitmap_is_empty(&type->types)) {
			cil_indent(indent);
			cil_printf("(typeattributeset %s (", key);
			ebitmap_to_cil(pdb, &type->types, SYM_TYPES);
			cil_printf("))\n");
		}
		break;
	case TYPE_ALIAS:
		break;
	default:
		log_err("Unknown flavor (%i) of type %s", type->flavor, key);
		rc = -1;
		goto exit;
	}

	rc = 0;

exit:
	return rc;
}

static int user_to_cil(int indent, struct policydb *pdb, struct avrule_block *block, struct stack *UNUSED(decl_stack), char *key, void *datum,  int scope)
{
	struct user_datum *user = datum;
	struct ebitmap roles = user->roles.roles;
	struct mls_semantic_level level = user->dfltlevel;
	struct mls_semantic_range range = user->range;
	struct ebitmap_node *node;
	uint32_t i;
	int sens_offset = 1;

	if (scope == SCOPE_DECL) {
		cil_println(indent, "(user %s)", key);
		// object_r is implicit in checkmodule, but not with CIL, create it
		// as part of base
		cil_println(indent, "(userrole %s " DEFAULT_OBJECT ")", key);
	}

	ebitmap_for_each_positive_bit(&roles, node, i) {
		cil_println(indent, "(userrole %s %s)", key, pdb->p_role_val_to_name[i]);
	}

	if (block->flags & AVRULE_OPTIONAL) {
		// sensitivites in user statements in optionals do not have the
		// standard -1 offset
		sens_offset = 0;
	}

	cil_indent(indent);
	cil_printf("(userlevel %s ", key);
	if (pdb->mls) {
		semantic_level_to_cil(pdb, sens_offset, &level);
	} else {
		cil_printf(DEFAULT_LEVEL);
	}
	cil_printf(")\n");

	cil_indent(indent);
	cil_printf("(userrange %s (", key);
	if (pdb->mls) {
		semantic_level_to_cil(pdb, sens_offset, &range.level[0]);
		cil_printf(" ");
		semantic_level_to_cil(pdb, sens_offset, &range.level[1]);
	} else {
		cil_printf(DEFAULT_LEVEL " " DEFAULT_LEVEL);
	}
	cil_printf("))\n");


	return 0;
}

static int boolean_to_cil(int indent, struct policydb *UNUSED(pdb), struct avrule_block *UNUSED(block), struct stack *UNUSED(decl_stack), char *key, void *datum,  int scope)
{
	struct cond_bool_datum *boolean = datum;
	const char *type;

	if (scope == SCOPE_DECL) {
		if (boolean->flags & COND_BOOL_FLAGS_TUNABLE) {
			type = "tunable";
		} else {
			type = "boolean";
		}

		cil_println(indent, "(%s %s %s)", type, key, boolean->state ? "true" : "false");
	}

	return 0;
}

static int sens_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *UNUSED(decl_stack), char *key, void *datum, int scope)
{
	struct level_datum *level = datum;

	if (scope == SCOPE_DECL) {
		if (!level->isalias) {
			cil_println(indent, "(sensitivity %s)", key);
		} else {
			cil_println(indent, "(sensitivityalias %s)", key);
			cil_println(indent, "(sensitivityaliasactual %s %s)", key, pdb->p_sens_val_to_name[level->level->sens - 1]);
		}
	}

	if (!ebitmap_is_empty(&level->level->cat)) {
		cil_indent(indent);
		cil_printf("(sensitivitycategory %s (", key);
		ebitmap_to_cil(pdb, &level->level->cat, SYM_CATS);
		cil_printf("))\n");
	}

	return 0;
}

static int sens_order_to_cil(int indent, struct policydb *pdb, struct ebitmap order)
{
	struct ebitmap_node *node;
	uint32_t i;

	if (ebitmap_is_empty(&order)) {
		return 0;
	}

	cil_indent(indent);
	cil_printf("(sensitivityorder (");

	ebitmap_for_each_positive_bit(&order, node, i) {
		cil_printf("%s ", pdb->p_sens_val_to_name[i]);
	}

	cil_printf("))\n");

	return 0;
}

static int cat_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *UNUSED(decl_stack), char *key, void *datum,  int scope)
{
	struct cat_datum *cat = datum;

	if (scope == SCOPE_REQ) {
		return 0;
	}

	if (!cat->isalias) {
		cil_println(indent, "(category %s)", key);
	} else {
		cil_println(indent, "(categoryalias %s)", key);
		cil_println(indent, "(categoryaliasactual %s %s)", key, pdb->p_cat_val_to_name[cat->s.value - 1]);
	}

	return 0;
}

static int cat_order_to_cil(int indent, struct policydb *pdb, struct ebitmap order)
{
	int rc = -1;
	struct ebitmap_node *node;
	uint32_t i;

	if (ebitmap_is_empty(&order)) {
		rc = 0;
		goto exit;
	}

	cil_indent(indent);
	cil_printf("(categoryorder (");

	ebitmap_for_each_positive_bit(&order, node, i) {
		cil_printf("%s ", pdb->p_cat_val_to_name[i]);
	}

	cil_printf("))\n");

	return 0;
exit:
	return rc;
}

static int polcaps_to_cil(struct policydb *pdb)
{
	int rc = -1;
	struct ebitmap *map;
	struct ebitmap_node *node;
	uint32_t i;
	const char *name;

	map = &pdb->policycaps;

	ebitmap_for_each_positive_bit(map, node, i) {
		name = sepol_polcap_getname(i);
		if (name == NULL) {
			log_err("Unknown policy capability id: %i", i);
			rc = -1;
			goto exit;
		}

		cil_println(0, "(policycap %s)", name);
	}

	return 0;
exit:
	return rc;
}

static int level_to_cil(struct policydb *pdb, struct mls_level *level)
{
	struct ebitmap *map = &level->cat;

	cil_printf("(%s", pdb->p_sens_val_to_name[level->sens - 1]);

	if (!ebitmap_is_empty(map)) {
		cil_printf("(");
		ebitmap_to_cil(pdb, map, SYM_CATS);
		cil_printf(")");
	}

	cil_printf(")");

	return 0;
}

static int context_to_cil(struct policydb *pdb, struct context_struct *con)
{
	cil_printf("(%s %s %s (",
		pdb->p_user_val_to_name[con->user - 1],
		pdb->p_role_val_to_name[con->role - 1],
		pdb->p_type_val_to_name[con->type - 1]);

	if (pdb->mls) {
		level_to_cil(pdb, &con->range.level[0]);
		cil_printf(" ");
		level_to_cil(pdb, &con->range.level[1]);
	} else {
		cil_printf(DEFAULT_LEVEL);
		cil_printf(" ");
		cil_printf(DEFAULT_LEVEL);
	}

	cil_printf("))");

	return 0;
}

static int ocontext_isid_to_cil(struct policydb *pdb, const char *const *sid_to_string,
				unsigned num_sids, struct ocontext *isids)
{
	int rc = -1;

	struct ocontext *isid;

	struct sid_item {
		char *sid_key;
		struct sid_item *next;
	};

	struct sid_item *head = NULL;
	struct sid_item *item = NULL;
	char *sid;
	char unknown[18];
	unsigned i;

	for (isid = isids; isid != NULL; isid = isid->next) {
		i = isid->sid[0];
		if (i < num_sids) {
			sid = (char*)sid_to_string[i];
		} else {
			snprintf(unknown, 18, "%s%u", "UNKNOWN", i);
			sid = unknown;
		}
		cil_println(0, "(sid %s)", sid);
		cil_printf("(sidcontext %s ", sid);
		context_to_cil(pdb, &isid->context[0]);
		cil_printf(")\n");

		// get the sid names in the correct order (reverse from the isids
		// ocontext) for sidorder statement
		item = malloc(sizeof(*item));
		if (item == NULL) {
			log_err("Out of memory");
			rc = -1;
			goto exit;
		}
		item->sid_key = strdup(sid);
		item->next = head;
		head = item;
	}

	if (head != NULL) {
		cil_printf("(sidorder (");
		for (item = head; item != NULL; item = item->next) {
			cil_printf("%s ", item->sid_key);
		}
		cil_printf("))\n");
	}

	rc = 0;

exit:
	while(head) {
		item = head;
		head = item->next;
		free(item->sid_key);
		free(item);
	}
	return rc;
}

static int ocontext_selinux_isid_to_cil(struct policydb *pdb, struct ocontext *isids)
{
	int rc = -1;

	rc = ocontext_isid_to_cil(pdb, selinux_sid_to_str, SELINUX_SID_SZ, isids);
	if (rc != 0) {
		goto exit;
	}

	return 0;

exit:
	return rc;
}

static int ocontext_selinux_fs_to_cil(struct policydb *UNUSED(pdb), struct ocontext *fss)
{
	if (fss != NULL) {
		log_err("Warning: 'fscon' statement unsupported in CIL. Dropping from output.");
	}

	return 0;
}

static int ocontext_selinux_port_to_cil(struct policydb *pdb, struct ocontext *portcons)
{
	int rc = -1;
	struct ocontext *portcon;
	const char *protocol;
	uint16_t high;
	uint16_t low;

	for (portcon = portcons; portcon != NULL; portcon = portcon->next) {

		switch (portcon->u.port.protocol) {
		case IPPROTO_TCP: protocol = "tcp"; break;
		case IPPROTO_UDP: protocol = "udp"; break;
		case IPPROTO_DCCP: protocol = "dccp"; break;
		case IPPROTO_SCTP: protocol = "sctp"; break;
		default:
			log_err("Unknown portcon protocol: %i", portcon->u.port.protocol);
			rc = -1;
			goto exit;
		}

		low = portcon->u.port.low_port;
		high = portcon->u.port.high_port;

		if (low == high) {
			cil_printf("(portcon %s %i ", protocol, low);
		} else {
			cil_printf("(portcon %s (%i %i) ", protocol, low, high);
		}

		context_to_cil(pdb, &portcon->context[0]);

		cil_printf(")\n");
	}

	return 0;
exit:
	return rc;
}

static int ocontext_selinux_ibpkey_to_cil(struct policydb *pdb,
					struct ocontext *ibpkeycons)
{
	int rc = -1;
	struct ocontext *ibpkeycon;
	char subnet_prefix_str[INET6_ADDRSTRLEN];
	struct in6_addr subnet_prefix = IN6ADDR_ANY_INIT;
	uint16_t high;
	uint16_t low;

	for (ibpkeycon = ibpkeycons; ibpkeycon; ibpkeycon = ibpkeycon->next) {
		low = ibpkeycon->u.ibpkey.low_pkey;
		high = ibpkeycon->u.ibpkey.high_pkey;
		memcpy(&subnet_prefix.s6_addr, &ibpkeycon->u.ibpkey.subnet_prefix,
		       sizeof(ibpkeycon->u.ibpkey.subnet_prefix));

		if (inet_ntop(AF_INET6, &subnet_prefix.s6_addr,
			      subnet_prefix_str, INET6_ADDRSTRLEN) == NULL) {
			log_err("ibpkeycon subnet_prefix is invalid: %m");
			rc = -1;
			goto exit;
		}

		if (low == high)
			cil_printf("(ibpkeycon %s %i ", subnet_prefix_str, low);
		else
			cil_printf("(ibpkeycon %s (%i %i) ", subnet_prefix_str, low,
				   high);

		context_to_cil(pdb, &ibpkeycon->context[0]);

		cil_printf(")\n");
	}
	return 0;
exit:
	return rc;
}

static int ocontext_selinux_netif_to_cil(struct policydb *pdb, struct ocontext *netifs)
{
	struct ocontext *netif;

	for (netif = netifs; netif != NULL; netif = netif->next) {
		cil_printf("(netifcon %s ", netif->u.name);
		context_to_cil(pdb, &netif->context[0]);

		cil_printf(" ");
		context_to_cil(pdb, &netif->context[1]);
		cil_printf(")\n");
	}

	return 0;
}

static int ocontext_selinux_node_to_cil(struct policydb *pdb, struct ocontext *nodes)
{
	int rc = -1;
	struct ocontext *node;
	char addr[INET_ADDRSTRLEN];
	char mask[INET_ADDRSTRLEN];

	for (node = nodes; node != NULL; node = node->next) {
		if (inet_ntop(AF_INET, &node->u.node.addr, addr, INET_ADDRSTRLEN) == NULL) {
			log_err("Nodecon address is invalid: %m");
			rc = -1;
			goto exit;
		}

		if (inet_ntop(AF_INET, &node->u.node.mask, mask, INET_ADDRSTRLEN) == NULL) {
			log_err("Nodecon mask is invalid: %m");
			rc = -1;
			goto exit;
		}

		cil_printf("(nodecon (%s) (%s) ", addr, mask);

		context_to_cil(pdb, &node->context[0]);

		cil_printf(")\n");
	}

	return 0;
exit:
	return rc;
}

static int ocontext_selinux_node6_to_cil(struct policydb *pdb, struct ocontext *nodes)
{
	int rc = -1;
	struct ocontext *node;
	char addr[INET6_ADDRSTRLEN];
	char mask[INET6_ADDRSTRLEN];

	for (node = nodes; node != NULL; node = node->next) {
		if (inet_ntop(AF_INET6, &node->u.node6.addr, addr, INET6_ADDRSTRLEN) == NULL) {
			log_err("Nodecon address is invalid: %m");
			rc = -1;
			goto exit;
		}

		if (inet_ntop(AF_INET6, &node->u.node6.mask, mask, INET6_ADDRSTRLEN) == NULL) {
			log_err("Nodecon mask is invalid: %m");
			rc = -1;
			goto exit;
		}

		cil_printf("(nodecon (%s) (%s) ", addr, mask);

		context_to_cil(pdb, &node->context[0]);

		cil_printf(")\n");
	}

	return 0;
exit:
	return rc;
}

static int ocontext_selinux_ibendport_to_cil(struct policydb *pdb, struct ocontext *ibendports)
{
	struct ocontext *ibendport;

	for (ibendport = ibendports; ibendport; ibendport = ibendport->next) {
		cil_printf("(ibendportcon %s %u ", ibendport->u.ibendport.dev_name, ibendport->u.ibendport.port);
		context_to_cil(pdb, &ibendport->context[0]);

		cil_printf(")\n");
	}

	return 0;
}

static int ocontext_selinux_fsuse_to_cil(struct policydb *pdb, struct ocontext *fsuses)
{
	int rc = -1;
	struct ocontext *fsuse;
	const char *behavior;


	for (fsuse = fsuses; fsuse != NULL; fsuse = fsuse->next) {
		switch (fsuse->v.behavior) {
		case SECURITY_FS_USE_XATTR: behavior = "xattr"; break;
		case SECURITY_FS_USE_TRANS: behavior = "trans"; break;
		case SECURITY_FS_USE_TASK:  behavior = "task"; break;
		default:
			log_err("Unknown fsuse behavior: %i", fsuse->v.behavior);
			rc = -1;
			goto exit;
		}

		cil_printf("(fsuse %s %s ", behavior, fsuse->u.name);

		context_to_cil(pdb, &fsuse->context[0]);

		cil_printf(")\n");

	}

	return 0;
exit:
	return rc;
}


static int ocontext_xen_isid_to_cil(struct policydb *pdb, struct ocontext *isids)
{
	int rc = -1;

	rc = ocontext_isid_to_cil(pdb, xen_sid_to_str, XEN_SID_SZ, isids);
	if (rc != 0) {
		goto exit;
	}

	return 0;

exit:
	return rc;
}

static int ocontext_xen_pirq_to_cil(struct policydb *pdb, struct ocontext *pirqs)
{
	struct ocontext *pirq;

	for (pirq = pirqs; pirq != NULL; pirq = pirq->next) {
		cil_printf("(pirqcon %i ", pirq->u.pirq);
		context_to_cil(pdb, &pirq->context[0]);
		cil_printf(")\n");
	}

	return 0;
}

static int ocontext_xen_ioport_to_cil(struct policydb *pdb, struct ocontext *ioports)
{
	struct ocontext *ioport;
	uint32_t low;
	uint32_t high;

	for (ioport = ioports; ioport != NULL; ioport = ioport->next) {
		low = ioport->u.ioport.low_ioport;
		high = ioport->u.ioport.high_ioport;

		if (low == high) {
			cil_printf("(ioportcon 0x%x ", low);
		} else {
			cil_printf("(ioportcon (0x%x 0x%x) ", low, high);
		}

		context_to_cil(pdb, &ioport->context[0]);

		cil_printf(")\n");
	}

	return 0;
}

static int ocontext_xen_iomem_to_cil(struct policydb *pdb, struct ocontext *iomems)
{
	struct ocontext *iomem;
	uint64_t low;
	uint64_t high;

	for (iomem = iomems; iomem != NULL; iomem = iomem->next) {
		low = iomem->u.iomem.low_iomem;
		high = iomem->u.iomem.high_iomem;

		if (low == high) {
			cil_printf("(iomemcon 0x%"PRIx64" ", low);
		} else {
			cil_printf("(iomemcon (0x%"PRIx64" 0x%"PRIx64") ", low, high);
		}

		context_to_cil(pdb, &iomem->context[0]);

		cil_printf(")\n");
	}

	return 0;
}

static int ocontext_xen_pcidevice_to_cil(struct policydb *pdb, struct ocontext *pcids)
{
	struct ocontext *pcid;

	for (pcid = pcids; pcid != NULL; pcid = pcid->next) {
		cil_printf("(pcidevicecon 0x%lx ", (unsigned long)pcid->u.device);
		context_to_cil(pdb, &pcid->context[0]);
		cil_printf(")\n");
	}

	return 0;
}

static int ocontexts_to_cil(struct policydb *pdb)
{
	int rc = -1;
	int ocon;

	static int (**ocon_funcs)(struct policydb *pdb, struct ocontext *ocon);
	static int (*ocon_selinux_funcs[OCON_NUM])(struct policydb *pdb, struct ocontext *ocon) = {
		ocontext_selinux_isid_to_cil,
		ocontext_selinux_fs_to_cil,
		ocontext_selinux_port_to_cil,
		ocontext_selinux_netif_to_cil,
		ocontext_selinux_node_to_cil,
		ocontext_selinux_fsuse_to_cil,
		ocontext_selinux_node6_to_cil,
		ocontext_selinux_ibpkey_to_cil,
		ocontext_selinux_ibendport_to_cil,
	};
	static int (*ocon_xen_funcs[OCON_NUM])(struct policydb *pdb, struct ocontext *ocon) = {
		ocontext_xen_isid_to_cil,
		ocontext_xen_pirq_to_cil,
		ocontext_xen_ioport_to_cil,
		ocontext_xen_iomem_to_cil,
		ocontext_xen_pcidevice_to_cil,
		NULL,
		NULL,
	};

	switch (pdb->target_platform) {
	case SEPOL_TARGET_SELINUX:
		ocon_funcs = ocon_selinux_funcs;
		break;
	case SEPOL_TARGET_XEN:
		ocon_funcs = ocon_xen_funcs;
		break;
	default:
		log_err("Unknown target platform: %i", pdb->target_platform);
		rc = -1;
		goto exit;
	}

	for (ocon = 0; ocon < OCON_NUM; ocon++) {
		if (ocon_funcs[ocon] != NULL) {
			rc = ocon_funcs[ocon](pdb, pdb->ocontexts[ocon]);
			if (rc != 0) {
				goto exit;
			}
		}
	}

	return 0;
exit:
	return rc;
}

static int genfscon_to_cil(struct policydb *pdb)
{
	struct genfs *genfs;
	struct ocontext *ocon;
	uint32_t sclass;

	for (genfs = pdb->genfs; genfs != NULL; genfs = genfs->next) {
		for (ocon = genfs->head; ocon != NULL; ocon = ocon->next) {
			sclass = ocon->v.sclass;
			if (sclass) {
				const char *file_type;
				const char *class_name = pdb->p_class_val_to_name[sclass-1];
				if (strcmp(class_name, "file") == 0) {
					file_type = "file";
				} else if (strcmp(class_name, "dir") == 0) {
					file_type = "dir";
				} else if (strcmp(class_name, "chr_file") == 0) {
					file_type = "char";
				} else if (strcmp(class_name, "blk_file") == 0) {
					file_type = "block";
				} else if (strcmp(class_name, "sock_file") == 0) {
					file_type = "socket";
				} else if (strcmp(class_name, "fifo_file") == 0) {
					file_type = "pipe";
				} else if (strcmp(class_name, "lnk_file") == 0) {
					file_type = "symlink";
				} else {
					return -1;
				}
				cil_printf("(genfscon %s \"%s\" %s ", genfs->fstype, ocon->u.name, file_type);
			} else {
				cil_printf("(genfscon %s \"%s\" ", genfs->fstype, ocon->u.name);
			}
			context_to_cil(pdb, &ocon->context[0]);
			cil_printf(")\n");
		}
	}

	return 0;
}

static int level_string_to_cil(char *levelstr)
{
	int rc = -1;
	char *sens = NULL;
	char *cats = NULL;
	int matched;
	char *saveptr = NULL;
	char *token = NULL;
	char *ranged = NULL;

	matched = tokenize(levelstr, ':', 2, &sens, &cats);
	if (matched < 1 || matched > 2) {
		log_err("Invalid level: %s", levelstr);
		rc = -1;
		goto exit;
	}

	cil_printf("(%s", sens);

	if (matched == 2) {
		cil_printf("(");
		token = strtok_r(cats, ",", &saveptr);
		while (token != NULL) {
			ranged = strchr(token, '.');
			if (ranged == NULL) {
				cil_printf("%s ", token);
			} else {
				*ranged = '\0';
				cil_printf("(range %s %s) ", token, ranged + 1);
			}
			token = strtok_r(NULL, ",", &saveptr);
		}
		cil_printf(")");
	}

	cil_printf(")");

	rc = 0;
exit:
	free(sens);
	free(cats);
	return rc;
}

static int level_range_string_to_cil(char *levelrangestr)
{
	char *ranged = NULL;
	char *low;
	char *high;

	ranged = strchr(levelrangestr, '-');
	if (ranged == NULL) {
		low = high = levelrangestr;
	} else {
		*ranged = '\0';
		low = levelrangestr;
		high = ranged + 1;
	}

	level_string_to_cil(low);
	cil_printf(" ");
	level_string_to_cil(high);

	return 0;
}

static int context_string_to_cil(char *contextstr)
{
	int rc = -1;
	int matched;
	char *user = NULL;
	char *role = NULL;
	char *type = NULL;
	char *level = NULL;

	matched = tokenize(contextstr, ':', 4, &user, &role, &type, &level);
	if (matched < 3 || matched > 4) {
		log_err("Invalid context: %s", contextstr);
		rc = -1;
		goto exit;
	}

	cil_printf("(%s %s %s (", user, role, type);

	if (matched == 3) {
		cil_printf(DEFAULT_LEVEL);
		cil_printf(" ");
		cil_printf(DEFAULT_LEVEL);
	} else {
		level_range_string_to_cil(level);
	}

	cil_printf("))");

	rc = 0;

exit:
	free(user);
	free(role);
	free(type);
	free(level);

	return rc;
}

static int seusers_to_cil(struct sepol_module_package *mod_pkg)
{
	int rc = -1;
	char *seusers = sepol_module_package_get_seusers(mod_pkg);
	size_t seusers_len = sepol_module_package_get_seusers_len(mod_pkg);
	char *cur = seusers;
	char *end = seusers + seusers_len;
	char *line = NULL;
	char *user = NULL;
	char *seuser = NULL;
	char *level = NULL;
	char *tmp = NULL;
	int matched;

	if (seusers_len == 0) {
		return 0;
	}

	while ((rc = get_line(&cur, end, &line)) > 0) {
		tmp = line;
		while (isspace(*tmp)) {
			tmp++;
		}

		if (tmp[0] == '#' || tmp[0] == '\0') {
			free(line);
			line = NULL;
			continue;
		}

		matched = tokenize(tmp, ':', 3, &user, &seuser, &level);

		if (matched < 2 || matched > 3) {
			log_err("Invalid seuser line: %s", line);
			rc = -1;
			goto exit;
		}

		if (!strcmp(user, "__default__")) {
			cil_printf("(selinuxuserdefault %s (", seuser);
		} else {
			cil_printf("(selinuxuser %s %s (", user, seuser);
		}

		switch (matched) {
		case 2:
			cil_printf("systemlow systemlow");
			break;
		case 3:
			level_range_string_to_cil(level);
			break;
		}

		cil_printf("))\n");

		free(user);
		free(seuser);
		free(level);
		free(line);
		user = seuser = level = NULL;
	}

	if (rc == -1) {
		cil_printf("Failed to read seusers\n");
		goto exit;
	}

	rc = 0;
exit:
	free(line);
	free(user);
	free(seuser);
	free(level);

	return rc;
}

static int netfilter_contexts_to_cil(struct sepol_module_package *mod_pkg)
{
	size_t netcons_len = sepol_module_package_get_netfilter_contexts_len(mod_pkg);

	if (netcons_len > 0) {
		log_err("Warning: netfilter_contexts are unsupported in CIL. Dropping from output.");
	}

	return 0;
}

static int user_extra_to_cil(struct sepol_module_package *mod_pkg)
{
	int rc = -1;
	char *userx = sepol_module_package_get_user_extra(mod_pkg);
	size_t userx_len = sepol_module_package_get_user_extra_len(mod_pkg);
	char *cur = userx;
	char *end = userx + userx_len;
	char *line;
	int matched;
	char *user = NULL;
	char *prefix = NULL;
	int prefix_len = 0;
	char *user_str = NULL;
	char *prefix_str = NULL;
	char *eol = NULL;
	char *tmp = NULL;

	if (userx_len == 0) {
		return 0;
	}

	while ((rc = get_line(&cur, end, &line)) > 0) {
		tmp = line;
		while (isspace(*tmp)) {
			tmp++;
		}

		if (tmp[0] == '#' || tmp[0] == '\0') {
			free(line);
			line = NULL;
			continue;
		}

		matched = tokenize(tmp, ' ', 4, &user_str, &user, &prefix_str, &prefix);
		if (matched != 4) {
			rc = -1;
			log_err("Invalid user extra line: %s", line);
			goto exit;
		}

		prefix_len = strlen(prefix);
		eol = prefix + prefix_len - 1;
		if (*eol != ';' || strcmp(user_str, "user") || strcmp(prefix_str, "prefix")) {
			rc = -1;
			log_err("Invalid user extra line: %s", line);
			goto exit;
		}
		*eol = '\0';

		cil_println(0, "(userprefix %s %s)", user, prefix);
		free(user);
		free(prefix);
		free(line);
		free(user_str);
		free(prefix_str);
		user = prefix = line = user_str = prefix_str = NULL;
	}

	if (rc == -1) {
		cil_printf("Failed to read user_extra\n");
		goto exit;
	}

	rc = 0;
exit:
	free(line);
	free(user);
	free(prefix);

	return rc;
}

static int file_contexts_to_cil(struct sepol_module_package *mod_pkg)
{
	int rc = -1;
	char *fc = sepol_module_package_get_file_contexts(mod_pkg);
	size_t fc_len = sepol_module_package_get_file_contexts_len(mod_pkg);
	char *cur = fc;
	char *end = fc + fc_len;
	char *line = NULL;
	int matched;
	char *regex = NULL;
	char *mode = NULL;
	char *context = NULL;
	const char *cilmode;
	char *tmp = NULL;

	if (fc_len == 0) {
		return 0;
	}

	while ((rc = get_line(&cur, end, &line)) > 0) {
		tmp = line;
		while (isspace(*tmp)) {
			tmp++;
		}

		if (tmp[0] == '#' || tmp[0] == '\0') {
			free(line);
			line = NULL;
			continue;
		}

		matched = tokenize(tmp, ' ', 3, &regex, &mode, &context);
		if (matched < 2 || matched > 3) {
			rc = -1;
			log_err("Invalid file context line: %s", line);
			goto exit;
		}

		if (matched == 2) {
			context = mode;
			mode = NULL;
		}

		if (mode == NULL) {
			cilmode = "any";
		} else if (!strcmp(mode, "--")) {
			cilmode = "file";
		} else if (!strcmp(mode, "-d")) {
			cilmode = "dir";
		} else if (!strcmp(mode, "-c")) {
			cilmode = "char";
		} else if (!strcmp(mode, "-b")) {
			cilmode = "block";
		} else if (!strcmp(mode, "-s")) {
			cilmode = "socket";
		} else if (!strcmp(mode, "-p")) {
			cilmode = "pipe";
		} else if (!strcmp(mode, "-l")) {
			cilmode = "symlink";
		} else {
			rc = -1;
			log_err("Invalid mode in file context line: %s", line);
			goto exit;
		}

		cil_printf("(filecon \"%s\" %s ", regex, cilmode);

		if (!strcmp(context, "<<none>>")) {
			cil_printf("()");
		} else {
			context_string_to_cil(context);
		}

		cil_printf(")\n");

		free(regex);
		free(mode);
		free(context);
		free(line);
		regex = mode = context = line = NULL;
	}

	if (rc == -1) {
		cil_printf("Failed to read file_contexts_to_cil\n");
		goto exit;
	}

	rc = 0;
exit:
	free(line);
	free(regex);
	free(mode);
	free(context);

	return rc;
}


static int (*func_to_cil[SYM_NUM])(int indent, struct policydb *pdb, struct avrule_block *block, struct stack *decl_stack, char *key, void *datum, int scope) = {
	NULL,	// commons, only stored in the global symtab, handled elsewhere
	class_to_cil,
	role_to_cil,
	type_to_cil,
	user_to_cil,
	boolean_to_cil,
	sens_to_cil,
	cat_to_cil
};

static int typealiases_to_cil(int indent, struct policydb *pdb, struct avrule_block *UNUSED(block), struct stack *decl_stack)
{
	struct type_datum *alias_datum;
	char *alias_name;
	char *type_name;
	struct list_node *curr;
	struct avrule_decl *decl = stack_peek(decl_stack);
	struct list *alias_list;
	int rc = -1;

	if (decl == NULL) {
		return -1;
	}

	alias_list = typealias_lists[decl->decl_id];
	if (alias_list == NULL) {
		return 0;
	}

	for (curr = alias_list->head; curr != NULL; curr = curr->next) {
		alias_name = curr->data;
		alias_datum = hashtab_search(pdb->p_types.table, alias_name);
		if (alias_datum == NULL) {
			rc = -1;
			goto exit;
		}
		if (alias_datum->flavor == TYPE_ALIAS) {
			type_name = pdb->p_type_val_to_name[alias_datum->primary - 1];
		} else {
			type_name = pdb->p_type_val_to_name[alias_datum->s.value - 1];
		}
		cil_println(indent, "(typealias %s)", alias_name);
		cil_println(indent, "(typealiasactual %s %s)", alias_name, type_name);
	}

	return 0;

exit:
	return rc;
}

static int declared_scopes_to_cil(int indent, struct policydb *pdb, struct avrule_block *block, struct stack *decl_stack)
{
	int rc = -1;
	struct ebitmap map;
	struct ebitmap_node *node;
	unsigned int i;
	char * key;
	struct scope_datum *scope;
	int sym;
	void *datum;
	struct avrule_decl *decl = stack_peek(decl_stack);

	for (sym = 0; sym < SYM_NUM; sym++) {
		if (func_to_cil[sym] == NULL) {
			continue;
		}

		map = decl->declared.scope[sym];
		ebitmap_for_each_positive_bit(&map, node, i) {
			key = pdb->sym_val_to_name[sym][i];
			datum = hashtab_search(pdb->symtab[sym].table, key);
			if (datum == NULL) {
				rc = -1;
				goto exit;
			}
			scope = hashtab_search(pdb->scope[sym].table, key);
			if (scope == NULL) {
				rc = -1;
				goto exit;
			}
			rc = func_to_cil[sym](indent, pdb, block, decl_stack, key, datum, scope->scope);
			if (rc != 0) {
				goto exit;
			}
		}

		if (sym == SYM_CATS) {
			rc = cat_order_to_cil(indent, pdb, map);
			if (rc != 0) {
				goto exit;
			}
		}

		if (sym == SYM_LEVELS) {
			rc = sens_order_to_cil(indent, pdb, map);
			if (rc != 0) {
				goto exit;
			}
		}

		if (sym == SYM_CLASSES) {
			rc = class_order_to_cil(indent, pdb, map);
			if (rc != 0) {
				goto exit;
			}
		}
	}

	return 0;
exit:
	return rc;
}

static int required_scopes_to_cil(int indent, struct policydb *pdb, struct avrule_block *block, struct stack *decl_stack)
{
	int rc = -1;
	struct ebitmap map;
	struct ebitmap_node *node;
	unsigned int i;
	unsigned int j;
	char * key;
	int sym;
	void *datum;
	struct avrule_decl *decl = stack_peek(decl_stack);
	struct scope_datum *scope_datum;

	for (sym = 0; sym < SYM_NUM; sym++) {
		if (func_to_cil[sym] == NULL) {
			continue;
		}

		map = decl->required.scope[sym];
		ebitmap_for_each_positive_bit(&map, node, i) {
			key = pdb->sym_val_to_name[sym][i];

			scope_datum = hashtab_search(pdb->scope[sym].table, key);
			if (scope_datum == NULL) {
				rc = -1;
				goto exit;
			}
			for (j = 0; j < scope_datum->decl_ids_len; j++) {
				if (scope_datum->decl_ids[j] == decl->decl_id) {
					break;
				}
			}
			if (j >= scope_datum->decl_ids_len) {
				// Symbols required in the global scope are also in the
				// required scope ebitmap of all avrule decls (i.e. required
				// in all optionals). So we need to look at the scopes of each
				// symbol in this avrule_decl to determine if it actually is
				// required in this decl, or if it's just required in the
				// global scope. If we got here, then this symbol is not
				// actually required in this scope, so skip it.
				continue;
			}

			datum = hashtab_search(pdb->symtab[sym].table, key);
			if (datum == NULL) {
				rc = -1;
				goto exit;
			}
			rc = func_to_cil[sym](indent, pdb, block, decl_stack, key, datum, SCOPE_REQ);
			if (rc != 0) {
				goto exit;
			}
		}
	}

	return 0;
exit:
	return rc;
}


static int additive_scopes_to_cil_map(char *key, void *data, void *arg)
{
	int rc = -1;
	struct map_args *args = arg;

	rc = func_to_cil[args->sym_index](args->indent, args->pdb, args->block, args->decl_stack, key, data, SCOPE_REQ);
	if (rc != 0) {
		goto exit;
	}

	return 0;

exit:
	return rc;
}

static int additive_scopes_to_cil(int indent, struct policydb *pdb, struct avrule_block *block, struct stack *decl_stack)
{
	int rc = -1;
	struct avrule_decl *decl = stack_peek(decl_stack);
	struct map_args args;
	args.pdb = pdb;
	args.block = block;
	args.decl_stack = decl_stack;
	args.indent = indent;

	for (args.sym_index = 0; args.sym_index < SYM_NUM; args.sym_index++) {
		if (func_to_cil[args.sym_index] == NULL) {
			continue;
		}
		rc = hashtab_map(decl->symtab[args.sym_index].table, additive_scopes_to_cil_map, &args);
		if (rc != 0) {
			goto exit;
		}
	}

	return 0;

exit:
	return rc;
}

static int is_scope_superset(struct scope_index *sup, struct scope_index *sub)
{
	// returns 1 if sup is a superset of sub, returns 0 otherwise

	int rc = 0;

	uint32_t i;
	struct ebitmap sup_map;
	struct ebitmap sub_map;
	struct ebitmap res;

	ebitmap_init(&res);

	for (i = 0; i < SYM_NUM; i++) {
		sup_map = sup->scope[i];
		sub_map = sub->scope[i];

		ebitmap_and(&res, &sup_map, &sub_map);
		if (!ebitmap_cmp(&res, &sub_map)) {
			goto exit;
		}
		ebitmap_destroy(&res);
	}

	if (sup->class_perms_len < sub->class_perms_len) {
		goto exit;
	}

	for (i = 0; i < sub->class_perms_len; i++) {
		sup_map = sup->class_perms_map[i];
		sub_map = sub->class_perms_map[i];

		ebitmap_and(&res, &sup_map, &sub_map);
		if (!ebitmap_cmp(&res, &sub_map)) {
			goto exit;
		}
		ebitmap_destroy(&res);
	}

	rc = 1;

exit:

	ebitmap_destroy(&res);
	return rc;
}

static int block_to_cil(struct policydb *pdb, struct avrule_block *block, struct stack *stack, int indent)
{
	int rc = -1;
	struct avrule_decl *decl;
	struct list *type_attr_list = NULL;
	struct list *role_attr_list = NULL;

	decl = block->branch_list;

	rc = list_init(&type_attr_list);
	if (rc != 0) {
		goto exit;
	}
	rc = list_init(&role_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = typealiases_to_cil(indent, pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	rc = declared_scopes_to_cil(indent, pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	rc = required_scopes_to_cil(indent, pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	rc = additive_scopes_to_cil(indent, pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	rc = avrule_list_to_cil(indent, pdb, decl->avrules, type_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = role_trans_to_cil(indent, pdb, decl->role_tr_rules, role_attr_list, type_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = role_allows_to_cil(indent, pdb, decl->role_allow_rules, role_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = range_trans_to_cil(indent, pdb, decl->range_tr_rules, type_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = filename_trans_to_cil(indent, pdb, decl->filename_trans_rules, type_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = cond_list_to_cil(indent, pdb, decl->cond_list, type_attr_list);
	if (rc != 0) {
		goto exit;
	}

	rc = cil_print_attr_list(indent, pdb, type_attr_list);
	if (rc != 0) {
		goto exit;
	}
	rc = cil_print_attr_list(indent, pdb, role_attr_list);
	if (rc != 0) {
		goto exit;
	}

exit:
	attr_list_destroy(&type_attr_list);
	attr_list_destroy(&role_attr_list);

	return rc;
}

static int module_block_to_cil(struct policydb *pdb, struct avrule_block *block, struct stack *stack, int *indent)
{
	int rc = 0;
	struct avrule_decl *decl;
	struct avrule_decl *decl_tmp;

	decl = block->branch_list;
	if (decl == NULL) {
		goto exit;
	}

	if (decl->next != NULL) {
		log_err("Warning: 'else' blocks in optional statements are unsupported in CIL. Dropping from output.");
	}

	if (block->flags & AVRULE_OPTIONAL) {
		while (stack->pos > 0) {
			decl_tmp = stack_peek(stack);
			if (is_scope_superset(&decl->required, &decl_tmp->required)) {
				break;
			}

			stack_pop(stack);
			(*indent)--;
			cil_println(*indent, ")");
		}

		cil_println(*indent, "(optional %s_optional_%i", pdb->name, decl->decl_id);
		(*indent)++;
	}

	stack_push(stack, decl);

	rc = block_to_cil(pdb, block, stack, *indent);
	if (rc != 0) {
		goto exit;
	}

exit:
	return rc;
}

static int global_block_to_cil(struct policydb *pdb, struct avrule_block *block, struct stack *stack)
{
	int rc = 0;
	struct avrule_decl *decl;

	decl = block->branch_list;
	if (decl == NULL) {
		goto exit;
	}

	if (decl->next != NULL) {
		log_err("Warning: 'else' not allowed in global block. Dropping from output.");
	}

	stack_push(stack, decl);

	// type aliases and commons are only stored in the global symtab.
	// However, to get scoping correct, we assume they are in the
	// global block
	rc = hashtab_map(pdb->p_commons.table, common_to_cil, NULL);
	if (rc != 0) {
		goto exit;
	}

	rc = block_to_cil(pdb, block, stack, 0);
	if (rc != 0) {
		goto exit;
	}

exit:
	return rc;
}

static int blocks_to_cil(struct policydb *pdb)
{
	int rc = -1;
	struct avrule_block *block;
	int indent = 0;
	struct stack *stack = NULL;

	rc = stack_init(&stack);
	if (rc != 0) {
		goto exit;
	}

	block = pdb->global;
	rc = global_block_to_cil(pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	for (block = block->next; block != NULL; block = block->next) {
		rc = module_block_to_cil(pdb, block, stack, &indent);
		if (rc != 0) {
			goto exit;
		}
	}

	while (indent > 0) {
		indent--;
		cil_println(indent, ")");
	}

exit:
	stack_destroy(&stack);

	return rc;
}

static int linked_block_to_cil(struct policydb *pdb, struct avrule_block *block, struct stack *stack)
{
	int rc = 0;
	struct avrule_decl *decl;

	decl = block->branch_list;
	if (decl == NULL) {
		goto exit;
	}

	if (!decl->enabled) {
		if (decl->next != NULL) {
			decl = decl->next;
		} else {
			goto exit;
		}
	}

	stack_push(stack, decl);

	rc = block_to_cil(pdb, block, stack, 0);
	if (rc != 0) {
		goto exit;
	}

	stack_pop(stack);

exit:
	return rc;
}

static int linked_blocks_to_cil(struct policydb *pdb)
{
	// Convert base module that has been linked to CIL
	// Since it is linked, all optional blocks have been resolved
	int rc = -1;
	struct avrule_block *block;
	struct stack *stack = NULL;

	rc = stack_init(&stack);
	if (rc != 0) {
		goto exit;
	}

	block = pdb->global;
	rc = global_block_to_cil(pdb, block, stack);
	if (rc != 0) {
		goto exit;
	}

	for (block = block->next; block != NULL; block = block->next) {
		rc = linked_block_to_cil(pdb, block, stack);
		if (rc != 0) {
			goto exit;
		}
	}

exit:
	stack_destroy(&stack);

	return rc;
}

static int handle_unknown_to_cil(struct policydb *pdb)
{
	int rc = -1;
	const char *hu;

	switch (pdb->handle_unknown) {
	case SEPOL_DENY_UNKNOWN:
		hu = "deny";
		break;
	case SEPOL_REJECT_UNKNOWN:
		hu = "reject";
		break;
	case SEPOL_ALLOW_UNKNOWN:
		hu = "allow";
		break;
	default:
		log_err("Unknown value for handle-unknown: %i", pdb->handle_unknown);
		rc = -1;
		goto exit;
	}

	cil_println(0, "(handleunknown %s)", hu);

	return 0;

exit:
	return rc;
}

static int generate_mls(struct policydb *pdb)
{
	const char *mls_str = pdb->mls ? "true" : "false";
	cil_println(0, "(mls %s)", mls_str);

	return 0;
}

static int generate_default_level(void)
{
	cil_println(0, "(sensitivity s0)");
	cil_println(0, "(sensitivityorder (s0))");
	cil_println(0, "(level " DEFAULT_LEVEL " (s0))");

	return 0;
}

static int generate_default_object(void)
{
	cil_println(0, "(role " DEFAULT_OBJECT ")");

	return 0;
}

static int generate_builtin_roles(void)
{
	// due to inconsistentencies between policies and CIL not allowing
	// duplicate roles, some roles are always created, regardless of if they
	// are declared in modules or not
	cil_println(0, "(role auditadm_r)");
	cil_println(0, "(role secadm_r)");

	return 0;
}

static int generate_gen_require_attribute(void)
{
	cil_println(0, "(typeattribute " GEN_REQUIRE_ATTR ")");
	cil_println(0, "(roleattribute " GEN_REQUIRE_ATTR ")");

	return 0;
}

static int fix_module_name(struct policydb *pdb)
{
	char *letter;
	int rc = -1;

	// The base module doesn't have its name set, but we use that for some
	// autogenerated names, like optionals and attributes, to prevent naming
	// collisions. However, they sometimes need to be fixed up.

	// the base module isn't given a name, so just call it "base"
	if (pdb->policy_type == POLICY_BASE) {
		pdb->name = strdup("base");
		if (pdb->name == NULL) {
			log_err("Out of memory");
			rc = -1;
			goto exit;
		}
	}

	// CIL is more restrictive in module names than checkmodule. Convert bad
	// characters to underscores
	for (letter = pdb->name; *letter != '\0'; letter++) {
		if (isalnum(*letter)) {
			continue;
		}

		*letter = '_';
	}

	return 0;
exit:
	return rc;
}

int sepol_module_policydb_to_cil(FILE *fp, struct policydb *pdb, int linked)
{
	int rc = -1;

	out_file = fp;

	if (pdb == NULL) {
		rc = 0;
		goto exit;
	}

	if (pdb->policy_type != SEPOL_POLICY_BASE &&
		pdb->policy_type != SEPOL_POLICY_MOD) {
		log_err("Policy package is not a base or module");
		rc = -1;
		goto exit;
	}

	rc = fix_module_name(pdb);
	if (rc != 0) {
		goto exit;
	}

	if (pdb->policy_type == SEPOL_POLICY_BASE && !pdb->mls) {
		// If this is a base non-mls policy, we need to define a default level
		// range that can be used for contexts by other non-mls modules, since
		// CIL requires that all contexts have a range, even if they are
		// ignored as in non-mls policies
		rc = generate_default_level();
		if (rc != 0) {
			goto exit;
		}
	}

	if (pdb->policy_type == SEPOL_POLICY_BASE) {
		// object_r is implicit in checkmodule, but not with CIL, create it
		// as part of base
		rc = generate_default_object();
		if (rc != 0) {
			goto exit;
		}

		rc = generate_builtin_roles();
		if (rc != 0) {
			goto exit;
		}

		// default attribute to be used to mimic gen_require in CIL
		rc = generate_gen_require_attribute();
		if (rc != 0) {
			goto exit;
		}

		// handle_unknown is used from only the base module
		rc = handle_unknown_to_cil(pdb);
		if (rc != 0) {
			goto exit;
		}

		// mls is used from only the base module
		rc = generate_mls(pdb);
		if (rc != 0) {
			goto exit;
		}
	}

	rc = role_list_create(pdb->p_roles.table);
	if (rc != 0) {
		goto exit;
	}

	rc = typealias_list_create(pdb);
	if (rc != 0) {
		goto exit;
	}

	rc = polcaps_to_cil(pdb);
	if (rc != 0) {
		goto exit;
	}

	rc = ocontexts_to_cil(pdb);
	if (rc != 0) {
		goto exit;
	}

	rc = genfscon_to_cil(pdb);
	if (rc != 0) {
		goto exit;
	}

	// now print everything that is scoped
	if (linked) {
		rc = linked_blocks_to_cil(pdb);
	} else {
		rc = blocks_to_cil(pdb);
	}
	if (rc != 0) {
		goto exit;
	}

	rc = 0;

exit:
	role_list_destroy();
	typealias_list_destroy();

	return rc;
}

int sepol_module_package_to_cil(FILE *fp, struct sepol_module_package *mod_pkg)
{
	int rc = -1;
	struct sepol_policydb *pdb;

	out_file = fp;

	pdb = sepol_module_package_get_policy(mod_pkg);
	if (pdb == NULL) {
		log_err("Failed to get policydb");
		rc = -1;
		goto exit;
	}

	rc = sepol_module_policydb_to_cil(fp, &pdb->p, 0);
	if (rc != 0) {
		goto exit;
	}

	rc = seusers_to_cil(mod_pkg);
	if (rc != 0) {
		goto exit;
	}

	rc = netfilter_contexts_to_cil(mod_pkg);
	if (rc != 0) {
		goto exit;
	}

	rc = user_extra_to_cil(mod_pkg);
	if (rc != 0) {
		goto exit;
	}

	rc = file_contexts_to_cil(mod_pkg);
	if (rc != 0) {
		goto exit;
	}

	rc = 0;

exit:
	return rc;
}

static int fp_to_buffer(FILE *fp, char **data, size_t *data_len)
{
	int rc = -1;
	char *d = NULL, *d_tmp;
	size_t d_len = 0;
	size_t read_len = 0;
	size_t max_len = 1 << 17; // start at 128KB, this is enough to hold about half of all the existing pp files

	d = malloc(max_len);
	if (d == NULL) {
		log_err("Out of memory");
		rc = -1;
		goto exit;
	}

	while ((read_len = fread(d + d_len, 1, max_len - d_len, fp)) > 0) {
		d_len += read_len;
		if (d_len == max_len) {
			max_len *= 2;
			d_tmp = realloc(d, max_len);
			if (d_tmp == NULL) {
				log_err("Out of memory");
				rc = -1;
				goto exit;
			}
			d = d_tmp;
		}
	}

	if (ferror(fp) != 0) {
		log_err("Failed to read pp file");
		rc = -1;
		goto exit;
	}

	*data = d;
	*data_len = d_len;

	return 0;

exit:
	free(d);
	return rc;
}

int sepol_ppfile_to_module_package(FILE *fp, struct sepol_module_package **mod_pkg)
{
	int rc = -1;
	struct sepol_policy_file *pf = NULL;
	struct sepol_module_package *pkg = NULL;
	char *data = NULL;
	size_t data_len;
	int fd;
	struct stat sb;

	rc = sepol_policy_file_create(&pf);
	if (rc != 0) {
		log_err("Failed to create policy file");
		goto exit;
	}

	fd = fileno(fp);
	if (fstat(fd, &sb) == -1) {
		rc = -1;
		goto exit;
	}

	if (S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode)) {
		// libsepol fails when trying to read a policy package from a pipe or a
		// socket due its use of lseek. In this case, read the data into a
		// buffer and provide that to libsepol
		rc = fp_to_buffer(fp, &data, &data_len);
		if (rc != 0) {
			goto exit;
		}

		sepol_policy_file_set_mem(pf, data, data_len);
	} else {
		sepol_policy_file_set_fp(pf, fp);
	}

	rc = sepol_module_package_create(&pkg);
	if (rc != 0) {
		log_err("Failed to create module package");
		goto exit;
	}

	rc = sepol_module_package_read(pkg, pf, 0);
	if (rc != 0) {
		log_err("Failed to read policy package");
		goto exit;
	}

	*mod_pkg = pkg;

exit:
	free(data);

	sepol_policy_file_free(pf);

	if (rc != 0) {
		sepol_module_package_free(pkg);
	}

	return rc;
}
