#include <stdlib.h>

#include "cil_flavor.h"
#include "cil_internal.h"
#include "cil_log.h"
#include "cil_tree.h"

struct cil_args_write {
	FILE *cil_out;
	struct cil_db *db;
};

static int cil_unfill_expr(struct cil_list *expr_str, char **out_str, int paren);
static int cil_unfill_classperms_list(struct cil_list *classperms, char **out_str, int paren);
static int __cil_write_first_child_helper(struct cil_tree_node *node, void *extra_args);
static int __cil_write_node_helper(struct cil_tree_node *node, uint32_t *finished, void *extra_args);
static int __cil_write_last_child_helper(struct cil_tree_node *node, void *extra_args);

static int __cil_strlist_concat(struct cil_list *str_list, char **out_str, int paren) {
	size_t len = paren ? 3 : 1;
	size_t num_elems = 0;
	char *p = NULL;
	struct cil_list_item *curr;

	/* get buffer size */
	cil_list_for_each(curr, str_list) {
		len += strlen((char *)curr->data);
		num_elems++;
	}
	if (num_elems != 0) {
		/* add spaces between elements */
		len += num_elems - 1;
	}
	*out_str = cil_malloc(len);
	p = *out_str;
	if (paren)
		*p++ = '(';
	cil_list_for_each(curr, str_list) {
		size_t src_len = strlen((char *)curr->data);
		memcpy(p, curr->data, src_len);
		p += src_len;
		if (curr->next != NULL)
			*p++ = ' ';
	}
	if (paren)
		*p++ = ')';
	*p++ = '\0';
	return SEPOL_OK;
}

static int __cil_unfill_expr_helper(struct cil_list_item *curr,
			     struct cil_list_item **next, char **out_str, int paren) {
	int rc = SEPOL_ERR;
	char *str = NULL;
	char *operand1 = NULL;
	char *operand2 = NULL;

	switch(curr->flavor) {
	case CIL_LIST:
		rc = cil_unfill_expr((struct cil_list *)curr->data, &str, paren);
		if (rc != SEPOL_OK)
			goto exit;
		*out_str = str;
		*next = curr->next;
		break;
	case CIL_STRING:
		str = strdup((char *)curr->data);
		if (!str) {
			cil_log(CIL_ERR, "OOM. Unable to copy string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
		*out_str = str;
		*next = curr->next;
		break;
	case CIL_DATUM:
		str = strdup(((struct cil_symtab_datum *)curr->data)->name);
		if (!str) {
			cil_log(CIL_ERR, "OOM. Unable to copy string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
		*out_str = str;
		*next = curr->next;
		break;
	case CIL_OP: {
		char *op_str = NULL;
		size_t len = 0;
		enum cil_flavor op_flavor = (enum cil_flavor)curr->data;
		switch (op_flavor) {
		case CIL_AND:
			op_str = CIL_KEY_AND;
			break;
		case CIL_OR:
			op_str = CIL_KEY_OR;
			break;
		case CIL_NOT:
			op_str = CIL_KEY_NOT;
			break;
		case CIL_ALL:
			op_str = CIL_KEY_ALL;
			break;
		case CIL_EQ:
			op_str = CIL_KEY_EQ;
			break;
		case CIL_NEQ:
			op_str = CIL_KEY_NEQ;
			break;
		case CIL_RANGE:
			op_str = CIL_KEY_RANGE;
			break;
		case CIL_XOR:
			op_str = CIL_KEY_XOR;
			break;
		case CIL_CONS_DOM:
			op_str = CIL_KEY_CONS_DOM;
			break;
		case CIL_CONS_DOMBY:
			op_str = CIL_KEY_CONS_DOMBY;
			break;
		case CIL_CONS_INCOMP:
			op_str = CIL_KEY_CONS_INCOMP;
			break;
		default:
			cil_log(CIL_ERR, "Unknown operator in expression: %d\n", op_flavor);
			goto exit;
			break;
		}
		/* all operands take two args except for 'all' and 'not', which take
		 * one and two, respectively */
		len = strlen(op_str) + 3;
		if (op_flavor == CIL_ALL) {
			*out_str = cil_malloc(len);
			sprintf(*out_str, "(%s)", op_str);
			*next = curr->next;
		} else if (op_flavor == CIL_NOT) {
			rc = __cil_unfill_expr_helper(curr->next, next, &operand1, paren);
			if (rc != SEPOL_OK)
				goto exit;
			len += strlen(operand1) + 1;
			*out_str = cil_malloc(len);
			sprintf(*out_str, "(%s %s)", op_str, operand1);
			// *next already set by recursive call
		} else {
			rc = __cil_unfill_expr_helper(curr->next, next, &operand1, paren);
			if (rc != SEPOL_OK)
				goto exit;
			len += strlen(operand1) + 1;
			// *next contains operand2, but keep track of next after that
			rc = __cil_unfill_expr_helper(*next, next, &operand2, paren);
			if (rc != SEPOL_OK)
				goto exit;
			len += strlen(operand2) + 1;
			*out_str = cil_malloc(len);
			sprintf(*out_str, "(%s %s %s)", op_str, operand1, operand2);
			// *next already set by recursive call
		}
	}
		break;
	case CIL_CONS_OPERAND: {
		enum cil_flavor operand_flavor = (enum cil_flavor)curr->data;
		char *operand_str = NULL;
		switch (operand_flavor) {
		case CIL_CONS_U1:
			operand_str = CIL_KEY_CONS_U1;
			break;
		case CIL_CONS_U2:
			operand_str = CIL_KEY_CONS_U2;
			break;
		case CIL_CONS_U3:
			operand_str = CIL_KEY_CONS_U3;
			break;
		case CIL_CONS_T1:
			operand_str = CIL_KEY_CONS_T1;
			break;
		case CIL_CONS_T2:
			operand_str = CIL_KEY_CONS_T2;
			break;
		case CIL_CONS_T3:
			operand_str = CIL_KEY_CONS_T3;
			break;
		case CIL_CONS_R1:
			operand_str = CIL_KEY_CONS_R1;
			break;
		case CIL_CONS_R2:
			operand_str = CIL_KEY_CONS_R2;
			break;
		case CIL_CONS_R3:
			operand_str = CIL_KEY_CONS_R3;
			break;
		case CIL_CONS_L1:
			operand_str = CIL_KEY_CONS_L1;
			break;
		case CIL_CONS_L2:
			operand_str = CIL_KEY_CONS_L2;
			break;
		case CIL_CONS_H1:
			operand_str = CIL_KEY_CONS_H1;
			break;
		case CIL_CONS_H2:
			operand_str = CIL_KEY_CONS_H2;
			break;
		default:
			cil_log(CIL_ERR, "Unknown operand in expression\n");
			goto exit;
			break;
		}
		str = strdup(operand_str);
		if (!str) {
			cil_log(CIL_ERR, "OOM. Unable to copy string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
		*out_str = str;
		*next = curr->next;
	}
		break;
	default:
		cil_log(CIL_ERR, "Unknown flavor in expression\n");
		goto exit;
		break;
	}
	rc = SEPOL_OK;
exit:
	free(operand1);
	free(operand2);
	return rc;
}

static int cil_unfill_expr(struct cil_list *expr_str, char **out_str, int paren) {
	int rc = SEPOL_ERR;

	/* reuse cil_list to keep track of strings */
	struct cil_list *str_list = NULL;
	struct cil_list_item *curr = NULL;

	cil_list_init(&str_list, CIL_NONE);

	/* iterate through cil_list, grabbing elements as needed */
	curr = expr_str->head;
	while(curr != NULL) {
		char *str = NULL;
		struct cil_list_item *next = NULL;

		rc = __cil_unfill_expr_helper(curr, &next, &str, paren);
        if (rc != SEPOL_OK)
            goto exit;
		cil_list_append(str_list, CIL_STRING, (void *) str);
		str = NULL;
		curr = next;
	}
	rc = __cil_strlist_concat(str_list, out_str, paren);
	if (rc != SEPOL_OK)
		goto exit;
	rc = SEPOL_OK;
exit:
	cil_list_for_each(curr, str_list) {
		free(curr->data);
	}
	cil_list_destroy(&str_list, 0);
	return rc;
}

static int cil_unfill_cats(struct cil_cats *cats, char **out_str) {
	return cil_unfill_expr(cats->str_expr, out_str, 0);
}

static int cil_unfill_level(struct cil_level *lvl, char **out_str) {
	int rc = SEPOL_ERR;
	size_t len = 0;
	char *sens, *cats = NULL;
	sens = lvl->sens_str;
	len = strlen(sens) + 3; // '()\0'
	if (lvl->cats != NULL) {
		rc = cil_unfill_cats(lvl->cats, &cats);
		if (rc != SEPOL_OK)
			goto exit;
		len += strlen(cats) + 1;
	}
	*out_str = cil_malloc(len);
	if (cats == NULL) {
		if (sprintf(*out_str, "(%s)", sens) < 0) {
			cil_log(CIL_ERR, "Error unpacking and writing level\n");
			rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		if (sprintf(*out_str, "(%s %s)", sens, cats) < 0) {
			cil_log(CIL_ERR, "Error unpacking and writing level\n");
			rc = SEPOL_ERR;
			goto exit;
		}
	}
	rc = SEPOL_OK;
exit:
	free(cats);
	return rc;
}

static int cil_unfill_levelrange(struct cil_levelrange *lvlrnge, char **out_str) {
	int rc = SEPOL_ERR;
	size_t len = 0;
	char *low = NULL, *high = NULL;
	if (lvlrnge->low_str != NULL) {
		low = strdup(lvlrnge->low_str);
		if (low == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy level string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_level(lvlrnge->low, &low);
		if (rc != SEPOL_OK)
			goto exit;
	}
	if (lvlrnge->high_str != NULL) {
		high = strdup(lvlrnge->high_str);
		if (high == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy level string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_level(lvlrnge->high, &high);
		if (rc != SEPOL_OK)
			goto exit;
	}
	len = strlen(low) + strlen(high) + 4;
	*out_str = cil_malloc(len);
	if (sprintf(*out_str, "(%s %s)", low, high) < 0) {
		cil_log(CIL_ERR, "Error unpacking and writing levelrange\n");
		rc = SEPOL_ERR;
		goto exit;
	}
	rc = SEPOL_OK;
exit:
	free(low);
	free(high);
	return rc;
}

static int cil_unfill_context(struct cil_context *context, char **out_str) {
	int rc = SEPOL_ERR;
	size_t len = 0;
	char *user_str, *role_str, *type_str;
	char *range_str = NULL;

	user_str = context->user_str;
	role_str = context->role_str;
	type_str = context->type_str;
	if (context->range_str != NULL) {
		range_str = strdup(context->range_str);
		if (range_str == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy range string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_levelrange(context->range, &range_str);
		if (rc != SEPOL_OK)
			goto exit;
	}
	len = strlen(user_str) + strlen(role_str) + strlen(type_str)
		+ strlen(range_str) + 6;
	*out_str = cil_malloc(len);
	if (sprintf(*out_str, "(%s %s %s %s)", user_str, role_str, type_str, range_str) < 0) {
		cil_log(CIL_ERR, "Error unpacking and writing context\n");
		rc = SEPOL_ERR;
		goto exit;
	}
	rc = SEPOL_OK;
exit:
	free(range_str);
	return rc;
}

static int cil_unfill_permx(struct cil_permissionx *permx, char **out_str) {
	size_t len = 3;
	int rc = SEPOL_ERR;
	char *kind, *obj;
	char *expr = NULL;

	switch (permx->kind) {
	case CIL_PERMX_KIND_IOCTL:
		kind = CIL_KEY_IOCTL;
		break;
	default:
		cil_log(CIL_ERR, "Unknown permissionx kind: %d\n", permx->kind);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	obj = permx->obj_str;
	rc = cil_unfill_expr(permx->expr_str, &expr, 1);
	if (rc != SEPOL_OK)
		goto exit;
	len += strlen(kind) + strlen(obj) + strlen(expr) + 2;
	*out_str = cil_malloc(len);
	if (sprintf(*out_str, "(%s %s %s)", kind, obj, expr) < 0) {
		cil_log(CIL_ERR, "Error writing xperm\n");
		rc = SEPOL_ERR;
		goto exit;
	}
	rc = SEPOL_OK;
exit:
	free(expr);
	return rc;
}

#define cil_write_unsupported(flavor) _cil_write_unsupported(flavor, __LINE__)
static int _cil_write_unsupported(const char *flavor, int line) {
	cil_log(CIL_ERR,
			"flavor \"%s\" is not supported, look in file \"%s\""
			" on line %d to add support.\n", flavor, __FILE__, line);
	return SEPOL_ENOTSUP;
}

static int cil_write_policycap(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_policycap *polcap = (struct cil_policycap *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_POLICYCAP, polcap->datum.name);
	return SEPOL_OK;
}

static int cil_write_perm(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_perm *perm = (struct cil_perm *)node->data;
	fprintf(cil_out, "%s", perm->datum.name);
	if (node->next != NULL)
		fprintf(cil_out, " ");
	return SEPOL_OK;
}


static int cil_write_class(struct cil_tree_node *node, uint32_t *finished,
		     struct cil_args_write *extra_args) {
	int rc = SEPOL_ERR;
	FILE *cil_out = extra_args->cil_out;
	struct cil_symtab_datum *datum = (struct cil_symtab_datum *)node->data;
	char *class_type = (node->flavor == CIL_CLASS) ? CIL_KEY_CLASS : CIL_KEY_COMMON;

	/* print preamble */
	fprintf(cil_out, "(%s %s ", class_type, datum->name);

	if (node->cl_head == NULL) {
		/* no associated perms in this part of tree */
		fprintf(cil_out, "()");
	} else {

		/* visit subtree (perms) */
		rc = cil_tree_walk(node, __cil_write_node_helper,
				   __cil_write_first_child_helper,
				   __cil_write_last_child_helper,
				   extra_args);
		if (rc != SEPOL_OK)
			goto exit;
	}

	/* postamble (trailing paren) */
	fprintf(cil_out, ")\n");
	*finished = CIL_TREE_SKIP_HEAD;
	rc = SEPOL_OK;
exit:
	return rc;
}

static int cil_write_classorder(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *ord_str = NULL;
	struct cil_classorder *classord = (struct cil_classorder *)node->data;

	/* cil_unfill_expr() has logic to stringify a cil_list, reuse that. */
	rc = cil_unfill_expr(classord->class_list_str, &ord_str, 1);
	if (rc != SEPOL_OK)
		goto exit;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_CLASSORDER, ord_str);
	rc = SEPOL_OK;
exit:
	free(ord_str);
	return rc;
}

static int cil_write_classcommon(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_classcommon *classcommon = (struct cil_classcommon *)node->data;
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_CLASSCOMMON, classcommon->class_str,
		classcommon->common_str);
	return SEPOL_OK;
}

static int cil_write_sid(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_sid *sid = (struct cil_sid *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_SID, sid->datum.name);
	return SEPOL_OK;
}

static int cil_write_sidcontext(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *sid;
	char *ctx_str = NULL;
	struct cil_sidcontext *sidcon = (struct cil_sidcontext *)node->data;

	sid = sidcon->sid_str;
	if (sidcon->context_str != NULL) {
		ctx_str = strdup(sidcon->context_str);
		if (ctx_str == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy context string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_context(sidcon->context, &ctx_str);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_SIDCONTEXT, sid, ctx_str);
	rc = SEPOL_OK;
exit:
	free(ctx_str);
	return rc;
}

static int cil_write_sidorder(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *ord_str = NULL;
	struct cil_sidorder *sidord = (struct cil_sidorder *)node->data;

	/* cil_unfill_expr() has logic to stringify a cil_list, reuse that. */
	rc = cil_unfill_expr(sidord->sid_list_str, &ord_str, 1);
	if (rc != SEPOL_OK)
		goto exit;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_SIDORDER, ord_str);
	rc = SEPOL_OK;
exit:
	free(ord_str);
	return rc;
}

static int cil_write_user(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_user *user = (struct cil_user *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_USER, user->datum.name);
	return SEPOL_OK;
}

static int cil_write_userrole(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_userrole *userrole = (struct cil_userrole *)node->data;
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_USERROLE, userrole->user_str,
		userrole->role_str);
	return SEPOL_OK;
}

static int cil_write_userlevel(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_userlevel *usrlvl = (struct cil_userlevel *)node->data;
	int rc = SEPOL_ERR;
	char *usr;
	char *lvl = NULL;

	usr = usrlvl->user_str;
	if (usrlvl->level_str != NULL) {
		lvl = strdup(usrlvl->level_str);
		if (lvl == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy level string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_level(usrlvl->level, &lvl);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_USERLEVEL, usr, lvl);
	rc = SEPOL_OK;
exit:
	free(lvl);
	return rc;
}

static int cil_write_userrange(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_userrange *usrrng = (struct cil_userrange *)node->data;
	int rc = SEPOL_ERR;
	char *usr;
	char *range = NULL;

	usr = usrrng->user_str;
	if (usrrng->range_str != NULL) {
		range = strdup(usrrng->range_str);
		if (range == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy levelrange string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_levelrange(usrrng->range, &range);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_USERRANGE, usr, range);
	rc = SEPOL_OK;
exit:
	free(range);
	return rc;
}

static int cil_write_role(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_role *role = (struct cil_role *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_ROLE, role->datum.name);
	return SEPOL_OK;
}

static int cil_write_roletype(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_roletype *roletype = (struct cil_roletype *)node->data;
	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_ROLETYPE, roletype->role_str, roletype->type_str);
	return SEPOL_OK;
}

static int cil_write_roleattribute(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_roleattribute *roleattr = (struct cil_roleattribute *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_ROLEATTRIBUTE, roleattr->datum.name);
	return SEPOL_OK;
}

static int cil_write_type(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_type *type = (struct cil_type *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_TYPE, type->datum.name);
	return SEPOL_OK;
}

static int cil_write_typepermissive(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_typepermissive *type = (struct cil_typepermissive *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_TYPEPERMISSIVE, type->type_str);
	return SEPOL_OK;
}

static int cil_write_typeattribute(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_typeattribute *typeattr = (struct cil_typeattribute *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_TYPEATTRIBUTE, typeattr->datum.name);
	return SEPOL_OK;
}

static int cil_write_typeattributeset(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *typeattr;
	char *set_str = NULL;
	struct cil_typeattributeset *typeattrset = (struct cil_typeattributeset *)node->data;

	typeattr = typeattrset->attr_str;
	rc = cil_unfill_expr(typeattrset->str_expr, &set_str, 1);
	if (rc != SEPOL_OK)
		goto exit;

	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_TYPEATTRIBUTESET, typeattr, set_str);
	rc = SEPOL_OK;
exit:
	free(set_str);
	return rc;
}

static int cil_write_expandtypeattribute(struct cil_tree_node *node, FILE *cil_out)
{
	int rc = SEPOL_ERR;
	char *attr_strs = NULL;
	struct cil_expandtypeattribute *expandattr = (struct cil_expandtypeattribute *)node->data;

	rc = cil_unfill_expr(expandattr->attr_strs, &attr_strs, 1);
	if (rc != SEPOL_OK)
		goto exit;

	fprintf(cil_out, "(%s %s %s)\n", CIL_KEY_EXPANDTYPEATTRIBUTE, attr_strs,
		expandattr->expand ? CIL_KEY_CONDTRUE : CIL_KEY_CONDFALSE);
	rc = SEPOL_OK;
exit:
	free(attr_strs);
	return rc;
}

static int cil_write_alias(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *type;
	struct cil_alias *alias = (struct cil_alias *)node->data;

	switch (node->flavor) {
	case CIL_TYPEALIAS:
		type = CIL_KEY_TYPEALIAS;
		break;
	case CIL_SENSALIAS:
		type = CIL_KEY_SENSALIAS;
		break;
	case CIL_CATALIAS:
		type = CIL_KEY_CATALIAS;
		break;
	default:
		cil_log(CIL_ERR, "Unknown alias type: %d\n", node->flavor);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	fprintf(cil_out, "(%s %s)\n", type, alias->datum.name);
	rc = SEPOL_OK;
exit:
	return rc;
}

static int cil_write_aliasactual(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *type, *alias, *actual;
	struct cil_aliasactual *aliasact = (struct cil_aliasactual *)node->data;

	switch (node->flavor) {
	case CIL_TYPEALIASACTUAL:
		type = CIL_KEY_TYPEALIASACTUAL;
		break;
	case CIL_SENSALIASACTUAL:
		type = CIL_KEY_SENSALIASACTUAL;
		break;
	case CIL_CATALIASACTUAL:
		type = CIL_KEY_CATALIASACTUAL;
		break;
	default:
		cil_log(CIL_ERR, "Unknown alias type: %d\n", node->flavor);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	alias = aliasact->alias_str;
	actual = aliasact->actual_str;
	fprintf(cil_out, "(%s %s %s)\n", type, alias, actual);
	rc = SEPOL_OK;
exit:
	return rc;
}

static int cil_write_nametypetransition(struct cil_tree_node *node, FILE *cil_out) {
	char *src, *tgt, *obj, *res, *name;
	struct cil_nametypetransition *ntrans = (struct cil_nametypetransition *)node->data;

	src = ntrans->src_str;
	tgt = ntrans->tgt_str;
	obj = ntrans->obj_str;
	res = ntrans->result_str;
	name = ntrans->name_str;
	fprintf(cil_out, "(%s %s %s %s \"%s\" %s)\n", CIL_KEY_TYPETRANSITION,
		src, tgt, obj, name, res);
	return SEPOL_OK;
}

static int cil_write_avrule_x(struct cil_avrule *avrule, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *rulekind, *src, *tgt;
	char *xperms = NULL;

	switch (avrule->rule_kind) {
	case CIL_AVRULE_ALLOWED:
		rulekind = CIL_KEY_ALLOWX;
		break;
	case CIL_AVRULE_AUDITALLOW:
		rulekind = CIL_KEY_AUDITALLOWX;
		break;
	case CIL_AVRULE_DONTAUDIT:
		rulekind = CIL_KEY_DONTAUDITX;
		break;
	case CIL_AVRULE_NEVERALLOW:
		rulekind = CIL_KEY_NEVERALLOWX;
		break;
	default:
		cil_log(CIL_ERR, "Unknown AVRULE type: %d\n", avrule->rule_kind);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	src = avrule->src_str;
	tgt = avrule->tgt_str;

	if (avrule->perms.x.permx_str != NULL) {
		xperms = strdup(avrule->perms.x.permx_str);
		if (xperms == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy xperms string.\n");
			rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_permx(avrule->perms.x.permx, &xperms);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s %s)\n", rulekind, src, tgt, xperms);
	rc = SEPOL_OK;
exit:
	free(xperms);
	return rc;
}

static int cil_write_avrule_orig(struct cil_avrule *avrule, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *rulekind, *src, *tgt;
	char *classperms = NULL;

	switch (avrule->rule_kind) {
	case CIL_AVRULE_ALLOWED:
		rulekind = CIL_KEY_ALLOW;
		break;
	case CIL_AVRULE_AUDITALLOW:
		rulekind = CIL_KEY_AUDITALLOW;
		break;
	case CIL_AVRULE_DONTAUDIT:
		rulekind = CIL_KEY_DONTAUDIT;
		break;
	case CIL_AVRULE_NEVERALLOW:
		rulekind = CIL_KEY_NEVERALLOW;
		break;
	default:
		cil_log(CIL_ERR, "Unknown AVRULE type: %d\n", avrule->rule_kind);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	src = avrule->src_str;
	tgt = avrule->tgt_str;

	rc = cil_unfill_classperms_list(avrule->perms.classperms, &classperms, 0);
	if (rc != SEPOL_OK)
		goto exit;
	fprintf(cil_out, "(%s %s %s %s)\n", rulekind, src, tgt, classperms);
	rc = SEPOL_OK;
exit:
	free(classperms);
	return rc;
}

static int cil_write_avrule(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	struct cil_avrule *avrule = (struct cil_avrule *)node->data;

	if (avrule->is_extended)
		rc = cil_write_avrule_x(avrule, cil_out);
	else
		rc = cil_write_avrule_orig(avrule, cil_out);
	return rc;
}

static int cil_write_type_rule(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *type, *src, *tgt, *obj, *res;
	struct cil_type_rule *typerule = (struct cil_type_rule *)node->data;

	switch (typerule->rule_kind) {
	case CIL_TYPE_TRANSITION:
		type = CIL_KEY_TYPETRANSITION;
		break;
	case CIL_TYPE_MEMBER:
		type = CIL_KEY_TYPEMEMBER;
		break;
	case CIL_TYPE_CHANGE:
		type = CIL_KEY_TYPECHANGE;
		break;
	default:
		cil_log(CIL_ERR, "Unknown TYPERULE type: %d\n", typerule->rule_kind);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	src = typerule->src_str;
	tgt = typerule->tgt_str;
	obj = typerule->obj_str;
	res = typerule->result_str;
	fprintf(cil_out, "(%s %s %s %s %s)\n", type, src, tgt, obj, res);
	rc = SEPOL_OK;
exit:
	return rc;
}

static int cil_write_sens(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_sens *sens = (struct cil_sens *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_SENSITIVITY, sens->datum.name);
	return SEPOL_OK;
}

static int cil_write_cat(struct cil_tree_node *node, FILE *cil_out) {
	struct cil_cat *cat = (struct cil_cat *)node->data;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_CATEGORY, cat->datum.name);
	return SEPOL_OK;
}

static int cil_write_senscat(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *sens;
	char *cats = NULL;
	struct cil_senscat *senscat = (struct cil_senscat *)node->data;

	sens = senscat->sens_str;
	rc = cil_unfill_cats(senscat->cats, &cats);
	if (rc != SEPOL_OK)
		goto exit;
	/* TODO: deal with extra/missing parens */
	fprintf(cil_out, "(%s %s (%s))\n", CIL_KEY_SENSCAT, sens, cats);
	rc = SEPOL_OK;
exit:
	free(cats);
	return rc;
}

static int cil_write_catorder(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *ord_str = NULL;
	struct cil_catorder *catord = (struct cil_catorder *)node->data;

	/* cil_unfill_expr() has logic to stringify a cil_list, reuse that. */
	rc = cil_unfill_expr(catord->cat_list_str, &ord_str, 1);
	if (rc != SEPOL_OK)
		goto exit;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_CATORDER, ord_str);
	rc = SEPOL_OK;
exit:
	free(ord_str);
	return rc;
}

static int cil_write_sensorder(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *ord_str = NULL;
	struct cil_sensorder *sensord = (struct cil_sensorder *)node->data;

	/* cil_unfill_expr() has logic to stringify a cil_list, reuse that. */
	rc = cil_unfill_expr(sensord->sens_list_str, &ord_str, 1);
	if (rc != SEPOL_OK)
		goto exit;
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_SENSITIVITYORDER, ord_str);
	rc = SEPOL_OK;
exit:
	free(ord_str);
	return rc;
}

static int cil_write_genfscon(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	char *ctx_str = NULL;

	struct cil_genfscon *genfscon = (struct cil_genfscon *)node->data;
	if (genfscon->context_str != NULL) {
		ctx_str = strdup(genfscon->context_str);
		if (ctx_str == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy context string.\n");
            rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_context(genfscon->context, &ctx_str);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s %s)\n", CIL_KEY_GENFSCON, genfscon->fs_str,
            genfscon->path_str, ctx_str);
	rc = SEPOL_OK;
exit:
	free(ctx_str);
	return rc;
}

static int cil_unfill_classperms(struct cil_list_item *curr, char **out_str) {
	int rc = SEPOL_ERR;
	size_t len = 3;
	char *class_str;
	char *perms_str = NULL;
	struct cil_classperms *cp = (struct cil_classperms *)curr->data;

	class_str = cp->class_str;
	len += strlen(class_str) + 1;

	/* fill_perms just calls gen_expr */
	rc = cil_unfill_expr(cp->perm_strs, &perms_str, 1);
	if (rc != SEPOL_OK)
		goto exit;
	len += strlen(perms_str);
	*out_str = cil_malloc(len);
	sprintf(*out_str, "(%s %s)", class_str, perms_str);
	rc = SEPOL_OK;
exit:
	free(perms_str);
	return rc;
}

static int cil_unfill_classperms_list(struct cil_list *classperms, char **out_str, int paren) {
	int rc = SEPOL_ERR;
	struct cil_list_item *curr;
	char *str = NULL;

	/* reuse cil_list to keep track of strings */
	struct cil_list *str_list = NULL;
	cil_list_init(&str_list, CIL_NONE);
	cil_list_for_each(curr, classperms) {
		switch (curr->flavor) {
		case CIL_CLASSPERMS_SET:
			str = strdup(((struct cil_classperms_set *)curr->data)->set_str);
			if (str == NULL) {
				cil_log(CIL_ERR, "OOM. Unable to copy classpermset.\n");
                rc = SEPOL_ERR;
				goto exit;
			}
			break;
		case CIL_CLASSPERMS:
			rc = cil_unfill_classperms(curr, &str);
			if (rc != SEPOL_OK)
				goto exit;
			break;
		default:
			cil_log(CIL_ERR, "Unrecognized classperms flavor\n.");
			goto exit;
		}
		cil_list_append(str_list, CIL_STRING, (void *) str);
		str = NULL;
	}
	rc = __cil_strlist_concat(str_list, out_str, paren);
	if (rc != SEPOL_OK)
		goto exit;
	rc = SEPOL_OK;
exit:
	cil_list_for_each(curr, str_list) {
		free(curr->data);
	}
	cil_list_destroy(&str_list, 0);
	return rc;
}

static int cil_write_fsuse(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	struct cil_fsuse *fsuse = (struct cil_fsuse *)node->data;
	char *type, *fsname;
	char *ctx_str = NULL;

	switch(fsuse->type) {
	case CIL_FSUSE_XATTR:
		type = CIL_KEY_XATTR;
		break;
	case CIL_FSUSE_TASK:
		type = CIL_KEY_TASK;
		break;
	case CIL_FSUSE_TRANS:
		type = CIL_KEY_TRANS;
		break;
	default:
		cil_log(CIL_ERR, "Unrecognized fsuse type\n");
		rc = SEPOL_ERR;
		goto exit;
		break;
	}

	fsname = fsuse->fs_str;
	if (fsuse->context_str != NULL) {
		ctx_str = strdup(fsuse->context_str);
		if (ctx_str == NULL) {
			cil_log(CIL_ERR, "OOM. Unable to copy context string.\n");
			rc = SEPOL_ERR;
			goto exit;
		}
	} else {
		rc = cil_unfill_context(fsuse->context, &ctx_str);
		if (rc != SEPOL_OK)
			goto exit;
	}
	fprintf(cil_out, "(%s %s %s %s)\n", CIL_KEY_FSUSE, type, fsname, ctx_str);
exit:
	free(ctx_str);
	return rc;
}

static int cil_write_constrain(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_ERR;
	struct cil_constrain *cons = (struct cil_constrain *)node->data;
	char *flav;
	char *classperms = NULL;
	char *expr = NULL;

	flav = (node->flavor == CIL_CONSTRAIN) ? CIL_KEY_CONSTRAIN : CIL_KEY_MLSCONSTRAIN;

	rc = cil_unfill_classperms_list(cons->classperms, &classperms, 0);
	if (rc != SEPOL_OK)
		goto exit;

	rc = cil_unfill_expr(cons->str_expr, &expr, 0);
	if (rc != SEPOL_OK)
		goto exit;

	fprintf(cil_out, "(%s %s %s)\n", flav, classperms, expr);
exit:
	free(classperms);
	free(expr);
	return rc;
}

static int cil_write_handleunknown(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_OK;
	struct cil_handleunknown *handunknown = (struct cil_handleunknown *)node->data;
	char *val = NULL;
	switch (handunknown->handle_unknown) {
	case SEPOL_ALLOW_UNKNOWN:
		val = CIL_KEY_HANDLEUNKNOWN_ALLOW;
		break;
	case SEPOL_DENY_UNKNOWN:
		val = CIL_KEY_HANDLEUNKNOWN_DENY;
		break;
	case SEPOL_REJECT_UNKNOWN:
		val = CIL_KEY_HANDLEUNKNOWN_REJECT;
		break;
	default:
		cil_log(CIL_ERR, "Unknown handleunknown value: %d.\n",
			handunknown->handle_unknown);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_HANDLEUNKNOWN, val);
exit:
	return rc;
}

static int cil_write_mls(struct cil_tree_node *node, FILE *cil_out) {
	int rc = SEPOL_OK;
	struct cil_mls *mls = (struct cil_mls *)node->data;
	char *val = NULL;
	switch (mls->value) {
	case CIL_TRUE:
		val = CIL_KEY_CONDTRUE;
		break;
	case CIL_FALSE:
		val = CIL_KEY_CONDFALSE;
		break;
	default:
		cil_log(CIL_ERR, "Unknown mls value: %d.\n", mls->value);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
	fprintf(cil_out, "(%s %s)\n", CIL_KEY_MLS, val);
exit:
	return rc;
}

static int __cil_write_first_child_helper(struct cil_tree_node *node, void *extra_args)
{
	int rc = SEPOL_ERR;
	struct cil_args_write *args = (struct cil_args_write *) extra_args;
	FILE *cil_out = NULL;

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

	cil_out = args->cil_out;

	if (node->parent && node->parent->flavor != CIL_ROOT && node->parent->flavor != CIL_SRC_INFO)
		fprintf(cil_out,"(");
	rc = SEPOL_OK;
exit:
	return rc;
}

static int __cil_write_node_helper(struct cil_tree_node *node, uint32_t *finished, void *extra_args)
{
	int rc = SEPOL_OK;
	struct cil_args_write *args = NULL;
	FILE *cil_out = NULL;

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

	args = extra_args;
	cil_out = args->cil_out;

	switch (node->flavor) {
	case CIL_BLOCK:
		rc = cil_write_unsupported("CIL_BLOCK");
		break;
	case CIL_BLOCKABSTRACT:
		rc = cil_write_unsupported("CIL_BLOCKABSTRACT");
		break;
	case CIL_BLOCKINHERIT:
		rc = cil_write_unsupported("CIL_BLOCKINHERIT");
		break;
	case CIL_IN:
		rc = cil_write_unsupported("CIL_IN");
		break;
	case CIL_POLICYCAP:
		cil_write_policycap(node, cil_out);
		break;
	case CIL_PERM:
		rc = cil_write_perm(node, cil_out);
		break;
	case CIL_MAP_PERM:
		rc = cil_write_unsupported("CIL_MAP_PERM");
		break;
	case CIL_CLASSMAPPING:
		rc = cil_write_unsupported("CIL_CLASSMAPPING");
		break;
	case CIL_CLASS:
		rc = cil_write_class(node, finished, extra_args);
		break;
	case CIL_COMMON:
		rc = cil_write_class(node, finished, extra_args);
		break;
	case CIL_MAP_CLASS:
		rc = cil_write_unsupported("CIL_MAP_CLASS");
		break;
	case CIL_CLASSORDER:
		rc = cil_write_classorder(node, cil_out);
		break;
	case CIL_CLASSPERMISSION:
		rc = cil_write_unsupported("CIL_CLASSPERMISSION");
		break;
	case CIL_CLASSPERMISSIONSET:
		rc = cil_write_unsupported("CIL_CLASSPERMISSIONSET");
		break;
	case CIL_CLASSCOMMON:
		rc = cil_write_classcommon(node, cil_out);
		break;
	case CIL_SID:
		rc = cil_write_sid(node, cil_out);
		break;
	case CIL_SIDCONTEXT:
		rc = cil_write_sidcontext(node, cil_out);
		break;
	case CIL_SIDORDER:
		rc = cil_write_sidorder(node, cil_out);
		break;
	case CIL_USER:
		rc = cil_write_user(node, cil_out);
		break;
	case CIL_USERATTRIBUTE:
		rc = cil_write_unsupported("CIL_USERATTRIBUTE");
		break;
	case CIL_USERATTRIBUTESET:
		rc = cil_write_unsupported("CIL_USERATTRIBUTESET");
		break;
	case CIL_USERROLE:
		rc = cil_write_userrole(node, cil_out);
		break;
	case CIL_USERLEVEL:
		rc = cil_write_userlevel(node, cil_out);
		break;
	case CIL_USERRANGE:
		rc = cil_write_userrange(node, cil_out);
		break;
	case CIL_USERBOUNDS:
		rc = cil_write_unsupported("CIL_USERBOUNDS");
		break;
	case CIL_USERPREFIX:
		rc = cil_write_unsupported("CIL_USERPREFIX");
		break;
	case CIL_ROLE:
		rc = cil_write_role(node, cil_out);
		break;
	case CIL_ROLETYPE:
		rc = cil_write_roletype(node, cil_out);
		break;
	case CIL_ROLEBOUNDS:
		rc = cil_write_unsupported("CIL_ROLEBOUNDS");
		break;
	case CIL_ROLEATTRIBUTE:
		cil_write_roleattribute(node, cil_out);
		break;
	case CIL_ROLEATTRIBUTESET:
		rc = cil_write_unsupported("CIL_ROLEATTRIBUTESET");
		break;
	case CIL_ROLEALLOW:
		rc = cil_write_unsupported("CIL_ROLEALLOW");
		break;
	case CIL_TYPE:
		rc = cil_write_type(node, cil_out);
		break;
	case CIL_TYPEBOUNDS:
		rc = cil_write_unsupported("CIL_TYPEBOUNDS");
		break;
	case CIL_TYPEPERMISSIVE:
		rc = cil_write_typepermissive(node, cil_out);
		break;
	case CIL_TYPEATTRIBUTE:
		rc = cil_write_typeattribute(node, cil_out);
		break;
	case CIL_TYPEATTRIBUTESET:
		rc = cil_write_typeattributeset(node, cil_out);
		break;
    case CIL_EXPANDTYPEATTRIBUTE:
        rc = cil_write_expandtypeattribute(node, cil_out);
        break;
	case CIL_TYPEALIAS:
		rc = cil_write_alias(node, cil_out);
		break;
	case CIL_TYPEALIASACTUAL:
		rc = cil_write_aliasactual(node, cil_out);
		break;
	case CIL_ROLETRANSITION:
		rc = cil_write_unsupported("CIL_ROLETRANSITION");
		break;
	case CIL_NAMETYPETRANSITION:
		rc = cil_write_nametypetransition(node, cil_out);
		break;
	case CIL_RANGETRANSITION:
		rc = cil_write_unsupported("CIL_RANGETRANSITION");
		break;
	case CIL_TUNABLE:
		rc = cil_write_unsupported("CIL_TUNABLE");
		break;
	case CIL_BOOL:
		rc = cil_write_unsupported("CIL_BOOL");
		break;
	case CIL_AVRULE:
	case CIL_AVRULEX:
		rc = cil_write_avrule(node, cil_out);
		break;
	case CIL_PERMISSIONX:
		rc = cil_write_unsupported("CIL_PERMISSIONX");
		break;
	case CIL_TYPE_RULE:
		cil_write_type_rule(node, cil_out);
		break;
	case CIL_SENS:
		rc = cil_write_sens(node, cil_out);
		break;
	case CIL_SENSALIAS:
		rc = cil_write_alias(node, cil_out);
		break;
	case CIL_SENSALIASACTUAL:
		rc = cil_write_aliasactual(node, cil_out);
		break;
	case CIL_CAT:
		rc = cil_write_cat(node, cil_out);
		break;
	case CIL_CATALIAS:
		rc = cil_write_alias(node, cil_out);
		break;
	case CIL_CATALIASACTUAL:
		rc = cil_write_aliasactual(node, cil_out);
		break;
	case CIL_CATSET:
		rc = cil_write_unsupported("CIL_CATSET");
		break;
	case CIL_SENSCAT:
		rc = cil_write_senscat(node, cil_out);
		break;
	case CIL_CATORDER:
		rc = cil_write_catorder(node, cil_out);
		break;
	case CIL_SENSITIVITYORDER:
		rc = cil_write_sensorder(node, cil_out);
		break;
	case CIL_LEVEL:
		rc = cil_write_unsupported("CIL_LEVEL");
		break;
	case CIL_LEVELRANGE:
		rc = cil_write_unsupported("CIL_LEVELRANGE");
		break;
	case CIL_CONTEXT:
		rc = cil_write_unsupported("CIL_CONTEXT");
		break;
	case CIL_NETIFCON:
		rc = cil_write_unsupported("CIL_NETIFCON");
		break;
	case CIL_GENFSCON:
		 rc = cil_write_genfscon(node, cil_out);
		break;
	case CIL_FILECON:
		rc = cil_write_unsupported("CIL_FILECON");
		break;
	case CIL_NODECON:
		rc = cil_write_unsupported("CIL_NODECON");
		break;
	case CIL_PORTCON:
		rc = cil_write_unsupported("CIL_PORTCON");
		break;
	case CIL_PIRQCON:
		rc = cil_write_unsupported("CIL_PIRQCON");
		break;
	case CIL_IOMEMCON:
		rc = cil_write_unsupported("CIL_IOMEMCON");
		break;
	case CIL_IOPORTCON:
		rc = cil_write_unsupported("CIL_IOPORTCON");
		break;
	case CIL_PCIDEVICECON:
		rc = cil_write_unsupported("CIL_PCIDEVICECON");
		break;
	case CIL_DEVICETREECON:
		rc = cil_write_unsupported("CIL_DEVICETREECON");
		break;
	case CIL_FSUSE:
		rc = cil_write_fsuse(node, cil_out);
		break;
	case CIL_CONSTRAIN:
		rc = cil_write_unsupported("CIL_CONSTRAIN");
		break;
	case CIL_MLSCONSTRAIN:
		rc = cil_write_constrain(node, cil_out);
		break;
	case CIL_VALIDATETRANS:
		rc = cil_write_unsupported("CIL_VALIDATETRANS");
		break;
	case CIL_MLSVALIDATETRANS:
		rc = cil_write_unsupported("CIL_MLSVALIDATETRANS");
		break;
	case CIL_CALL:
		rc = cil_write_unsupported("CIL_CALL");
		break;
	case CIL_MACRO:
		rc = cil_write_unsupported("CIL_MACRO");
		break;
	case CIL_NODE:
		rc = cil_write_unsupported("CIL_NODE");
		break;
	case CIL_OPTIONAL:
		rc = cil_write_unsupported("CIL_OPTIONAL");
		break;
	case CIL_IPADDR:
		rc = cil_write_unsupported("CIL_IPADDR");
		break;
	case CIL_CONDBLOCK:
		rc = cil_write_unsupported("CIL_CONDBLOCK");
		break;
	case CIL_BOOLEANIF:
		rc = cil_write_unsupported("CIL_BOOLEANIF");
		break;
	case CIL_TUNABLEIF:
		rc = cil_write_unsupported("CIL_TUNABLEIF");
		break;
	case CIL_DEFAULTUSER:
		rc = cil_write_unsupported("CIL_DEFAULTUSER");
		break;
	case CIL_DEFAULTROLE:
		rc = cil_write_unsupported("CIL_DEFAULTROLE");
		break;
	case CIL_DEFAULTTYPE:
		rc = cil_write_unsupported("CIL_DEFAULTTYPE");
		break;
	case CIL_DEFAULTRANGE:
		rc = cil_write_unsupported("CIL_DEFAULTRANGE");
		break;
	case CIL_SELINUXUSER:
		rc = cil_write_unsupported("CIL_SELINUXUSER");
		break;
	case CIL_SELINUXUSERDEFAULT:
		rc = cil_write_unsupported("CIL_SELINUXUSERDEFAULT");
		break;
	case CIL_HANDLEUNKNOWN:
		rc = cil_write_handleunknown(node, cil_out);
		break;
	case CIL_MLS:
		rc = cil_write_mls(node, cil_out);
		break;
	case CIL_SRC_INFO:
		break;
	case CIL_NONE:
		// TODO: add proper removal support
		*finished = CIL_TREE_SKIP_HEAD;
		break;
	default:
		cil_log(CIL_ERR, "Unknown AST flavor: %d.\n", node->flavor);
		rc = SEPOL_ERR;
		goto exit;
		break;
	}
exit:
	return rc;
}

static int __cil_write_last_child_helper(struct cil_tree_node *node, void *extra_args)
{
	int rc = SEPOL_ERR;
	struct cil_args_write *args = NULL;
	FILE *cil_out = NULL;

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

	args = extra_args;
	cil_out = args->cil_out;

	if (node->parent && node->parent->flavor != CIL_ROOT && node->parent->flavor != CIL_SRC_INFO) {
		fprintf(cil_out,")");
	}
	rc = SEPOL_OK;
exit:
	return rc;
}

/* main exported function */
int cil_write_ast(struct cil_db *db, const char* path) {
	int rc = SEPOL_ERR;
	struct cil_args_write extra_args;
	FILE *cil_out = NULL;

	cil_out = fopen(path, "we");
	if (cil_out == NULL) {
		cil_log(CIL_ERR, "Failure opening output file for writing AST\n");
		rc = SEPOL_ERR;
		goto exit;
	}

	extra_args.cil_out = cil_out;
	extra_args.db = db;
	rc = cil_tree_walk(db->ast->root, __cil_write_node_helper,
			   __cil_write_first_child_helper,
			   __cil_write_last_child_helper,
			   &extra_args);
	if (rc != SEPOL_OK) {
		cil_log(CIL_INFO, "cil_tree_walk failed, rc: %d\n", rc);
		goto exit;
	}

exit:
	fclose(cil_out);
	cil_out = NULL;
	return rc;
}