diff --git a/checkpolicy/checkmodule.c b/checkpolicy/checkmodule.c
index 3432608b..d68a56a1 100644
--- a/checkpolicy/checkmodule.c
+++ b/checkpolicy/checkmodule.c
@@ -275,6 +275,15 @@ int main(int argc, char **argv)
 		modpolicydb.mls = mlspol;
 		modpolicydb.handle_unknown = handle_unknown;
 
+		/*
+		 * Init and alloc te_avtab for filename transition duplicate
+		 * checking
+		 */
+		if (avtab_init(&modpolicydb.te_avtab))
+			exit(1);
+		if (avtab_alloc(&modpolicydb.te_avtab, 1 << 11))
+			exit(1);
+
 		if (read_source_policy(&modpolicydb, file, argv[0]) == -1) {
 			exit(1);
 		}
diff --git a/checkpolicy/checkpolicy.c b/checkpolicy/checkpolicy.c
index 2485142d..6e8ed833 100644
--- a/checkpolicy/checkpolicy.c
+++ b/checkpolicy/checkpolicy.c
@@ -611,6 +611,15 @@ int main(int argc, char **argv)
 		parse_policy.mls = mlspol;
 		parse_policy.handle_unknown = handle_unknown;
 
+		/*
+		 * Init and alloc te_avtab for filename transition duplicate
+		 * checking
+		 */
+		if (avtab_init(&parse_policy.te_avtab))
+			exit(1);
+		if (avtab_alloc(&parse_policy.te_avtab, 1 << 11))
+			exit(1);
+
 		policydbp = &parse_policy;
 
 		if (read_source_policy(policydbp, file, "checkpolicy") < 0)
diff --git a/checkpolicy/policy_define.c b/checkpolicy/policy_define.c
index cef8f3c4..dc2ee8f3 100644
--- a/checkpolicy/policy_define.c
+++ b/checkpolicy/policy_define.c
@@ -3352,6 +3352,7 @@ int define_filename_trans(void)
 	ebitmap_node_t *snode, *tnode, *cnode;
 	filename_trans_rule_t *ftr;
 	type_datum_t *typdatum;
+	avtab_key_t avt_key;
 	uint32_t otype;
 	unsigned int c, s, t;
 	int add, self, rc;
@@ -3443,9 +3444,13 @@ int define_filename_trans(void)
 	ebitmap_for_each_positive_bit(&e_tclasses, cnode, c) {
 		ebitmap_for_each_positive_bit(&e_stypes, snode, s) {
 			ebitmap_for_each_positive_bit(&e_ttypes, tnode, t) {
-				rc = policydb_filetrans_insert(
-					policydbp, s+1, t+1, c+1, name,
-					NULL, otype, NULL
+				avt_key.specified = AVTAB_TRANSITION;
+				avt_key.source_type = s + 1;
+				avt_key.target_type = t + 1;
+				avt_key.target_class = c + 1;
+				rc = avtab_insert_filename_trans(
+					&policydbp->te_avtab, &avt_key, otype,
+					name, NULL
 				);
 				if (rc != SEPOL_OK) {
 					if (rc == SEPOL_EEXIST) {
@@ -3461,9 +3466,13 @@ int define_filename_trans(void)
 				}
 			}
 			if (self) {
-				rc = policydb_filetrans_insert(
-					policydbp, s+1, s+1, c+1, name,
-					NULL, otype, NULL
+				avt_key.specified = AVTAB_TRANSITION;
+				avt_key.source_type = s + 1;
+				avt_key.target_type = t + 1;
+				avt_key.target_class = c + 1;
+				rc = avtab_insert_filename_trans(
+					&policydbp->te_avtab, &avt_key, otype,
+					name, NULL
 				);
 				if (rc != SEPOL_OK) {
 					if (rc == SEPOL_EEXIST) {
diff --git a/checkpolicy/test/dispol.c b/checkpolicy/test/dispol.c
index de1a5d11..e86ad2ed 100644
--- a/checkpolicy/test/dispol.c
+++ b/checkpolicy/test/dispol.c
@@ -63,7 +63,6 @@ static struct command {
 	{CMD,       'a',  "display type attributes"},
 	{CMD,       'p',  "display the list of permissive types"},
 	{CMD,       'u',  "display unknown handling setting"},
-	{CMD,       'F',  "display filename_trans rules"},
 	{HEADER, 0, ""},
 	{CMD|NOOPT, 'f',  "set output file"},
 	{CMD|NOOPT, 'm',  "display menu"},
@@ -126,6 +125,26 @@ static int render_key(avtab_key_t * key, policydb_t * p, FILE * fp)
 	return 0;
 }
 
+typedef struct {
+	avtab_key_t *key;
+	policydb_t *p;
+	FILE *fp;
+} render_name_trans_args_t;
+
+static int render_name_trans_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
+{
+	char *name = k;
+	uint32_t *otype = d;
+	render_name_trans_args_t *args = a;
+
+	fprintf(args->fp, "type_transition ");
+	render_key(args->key, args->p, args->fp);
+	render_type(*otype, args->p, args->fp);
+	fprintf(args->fp, " \"%s\";\n", name);
+
+	return 0;
+}
+
 /* 'what' values for this function */
 #define	RENDER_UNCONDITIONAL	0x0001	/* render all regardless of enabled state */
 #define RENDER_ENABLED		0x0002
@@ -178,10 +197,19 @@ static int render_av_rule(avtab_key_t * key, avtab_datum_t * datum, uint32_t wha
 		}
 	} else if (key->specified & AVTAB_TYPE) {
 		if (key->specified & AVTAB_TRANSITION) {
-			fprintf(fp, "type_transition ");
-			render_key(key, p, fp);
-			render_type(datum->trans->otype, p, fp);
-			fprintf(fp, ";\n");
+			if (datum->trans->otype) {
+				fprintf(fp, "type_transition ");
+				render_key(key, p, fp);
+				render_type(datum->trans->otype, p, fp);
+				fprintf(fp, ";\n");
+			}
+			render_name_trans_args_t args = {
+				.key = key,
+				.p = p,
+				.fp = fp,
+			};
+			hashtab_map(datum->trans->name_trans.table,
+				    render_name_trans_helper, &args);
 		}
 		if (key->specified & AVTAB_MEMBER) {
 			fprintf(fp, "type_member ");
@@ -448,48 +476,6 @@ static void display_role_trans(policydb_t *p, FILE *fp)
 	}
 }
 
-struct filenametr_display_args {
-	policydb_t *p;
-	FILE *fp;
-};
-
-static int filenametr_display(hashtab_key_t key,
-			      hashtab_datum_t datum,
-			      void *ptr)
-{
-	struct filename_trans_key *ft = (struct filename_trans_key *)key;
-	struct filename_trans_datum *ftdatum = datum;
-	struct filenametr_display_args *args = ptr;
-	policydb_t *p = args->p;
-	FILE *fp = args->fp;
-	ebitmap_node_t *node;
-	uint32_t bit;
-
-	do {
-		ebitmap_for_each_positive_bit(&ftdatum->stypes, node, bit) {
-			display_id(p, fp, SYM_TYPES, bit, "");
-			display_id(p, fp, SYM_TYPES, ft->ttype - 1, "");
-			display_id(p, fp, SYM_CLASSES, ft->tclass - 1, ":");
-			display_id(p, fp, SYM_TYPES, ftdatum->otype - 1, "");
-			fprintf(fp, " %s\n", ft->name);
-		}
-		ftdatum = ftdatum->next;
-	} while (ftdatum);
-
-	return 0;
-}
-
-
-static void display_filename_trans(policydb_t *p, FILE *fp)
-{
-	struct filenametr_display_args args;
-
-	fprintf(fp, "filename_trans rules:\n");
-	args.p = p;
-	args.fp = fp;
-	hashtab_map(p->filename_trans, filenametr_display, &args);
-}
-
 static int menu(void)
 {
 	unsigned int i;
@@ -690,9 +676,6 @@ int main(int argc, char **argv)
 			if (out_fp != stdout)
 				printf("\nOutput to file: %s\n", OutfileName);
 			break;
-		case 'F':
-			display_filename_trans(&policydb, out_fp);
-			break;
 		case 'q':
 			policydb_destroy(&policydb);
 			exit(0);
diff --git a/libsepol/cil/src/cil_binary.c b/libsepol/cil/src/cil_binary.c
index 3f264594..7150d405 100644
--- a/libsepol/cil/src/cil_binary.c
+++ b/libsepol/cil/src/cil_binary.c
@@ -1005,7 +1005,12 @@ static int __cil_insert_type_rule(policydb_t *pdb, uint32_t kind, uint32_t src,
 	}
 	
 	existing = avtab_search_node(&pdb->te_avtab, &avtab_key);
-	if (existing) {
+	/*
+	 * There might be empty transition node containing filename transitions
+	 * only. That is okay, we can merge them later.
+	 */
+	if (existing && !(existing->key.specified & AVTAB_TRANSITION &&
+	    !existing->datum.trans->otype)) {
 		/* Don't add duplicate type rule and warn if they conflict.
 		 * A warning should have been previously given if there is a
 		 * non-duplicate rule using the same key.
@@ -1029,7 +1034,13 @@ static int __cil_insert_type_rule(policydb_t *pdb, uint32_t kind, uint32_t src,
 	}
 
 	if (!cond_node) {
-		rc = avtab_insert(&pdb->te_avtab, &avtab_key, &avtab_datum);
+		/* If we have node from empty filename transition, use it */
+		if (existing && existing->key.specified & AVTAB_TRANSITION &&
+		    !existing->datum.trans->otype)
+			existing->datum.trans->otype = avtab_datum.trans->otype;
+		else
+			rc = avtab_insert(&pdb->te_avtab, &avtab_key,
+					  &avtab_datum);
 	} else {
 		existing = avtab_search_node(&pdb->te_cond_avtab, &avtab_key);
 		if (existing) {
@@ -1189,16 +1200,18 @@ static int __cil_typetransition_to_avtab_helper(policydb_t *pdb,
 	class_datum_t *sepol_obj = NULL;
 	uint32_t otype;
 	struct cil_list_item *c;
+	avtab_key_t avt_key;
 
 	cil_list_for_each(c, class_list) {
 		rc = __cil_get_sepol_class_datum(pdb, DATUM(c->data), &sepol_obj);
 		if (rc != SEPOL_OK) return rc;
 
-		rc = policydb_filetrans_insert(
-			pdb, sepol_src->s.value, sepol_tgt->s.value,
-			sepol_obj->s.value, name, NULL,
-			sepol_result->s.value, &otype
-		);
+		avt_key.specified = AVTAB_TRANSITION;
+		avt_key.source_type = sepol_src->s.value;
+		avt_key.target_type = sepol_tgt->s.value;
+		avt_key.target_class = sepol_obj->s.value;
+		rc = avtab_insert_filename_trans(&pdb->te_avtab, &avt_key,
+			sepol_result->s.value, name, &otype);
 		if (rc != SEPOL_OK) {
 			if (rc == SEPOL_EEXIST) {
 				if (sepol_result->s.value!= otype) {
diff --git a/libsepol/include/sepol/policydb/avtab.h b/libsepol/include/sepol/policydb/avtab.h
index ca009c16..5dc720cc 100644
--- a/libsepol/include/sepol/policydb/avtab.h
+++ b/libsepol/include/sepol/policydb/avtab.h
@@ -42,6 +42,7 @@
 
 #include <sys/types.h>
 #include <stdint.h>
+#include <sepol/policydb/symtab.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -72,6 +73,7 @@ typedef struct avtab_key {
 
 typedef struct avtab_trans {
 	uint32_t otype;		/* resulting type of the new object */
+	symtab_t name_trans;	/* filename transitions */
 } avtab_trans_t;
 
 typedef struct avtab_extended_perms {
@@ -115,6 +117,8 @@ extern int avtab_insert(avtab_t * h, avtab_key_t * k, avtab_datum_t * d);
 
 extern avtab_datum_t *avtab_search(avtab_t * h, avtab_key_t * k);
 
+extern void avtab_trans_destroy(avtab_trans_t *trans);
+
 extern void avtab_destroy(avtab_t * h);
 
 extern int avtab_map(const avtab_t * h,
@@ -148,6 +152,11 @@ extern avtab_ptr_t avtab_search_node_next(avtab_ptr_t node, int specified);
 /* avtab_alloc uses one bucket per 2-4 elements, so adjust to get maximum buckets */
 #define MAX_AVTAB_SIZE (MAX_AVTAB_HASH_BUCKETS << 1)
 
+extern int avtab_insert_filename_trans(avtab_t *a, avtab_key_t *key,
+				       uint32_t otype, const char *name,
+				       uint32_t *present_otype);
+extern int avtab_filename_trans_read(void *fp, uint32_t vers, avtab_t *a);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libsepol/include/sepol/policydb/hashtab.h b/libsepol/include/sepol/policydb/hashtab.h
index dca8c983..354ebb43 100644
--- a/libsepol/include/sepol/policydb/hashtab.h
+++ b/libsepol/include/sepol/policydb/hashtab.h
@@ -110,6 +110,14 @@ extern int hashtab_map(hashtab_t h,
 
 extern void hashtab_hash_eval(hashtab_t h, char *tag);
 
+/* Returns number of elements in the hashtab h or 0 is h is NULL */
+static inline uint32_t hashtab_nel(hashtab_t h)
+{
+	if (!h)
+		return 0;
+	return h->nel;
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libsepol/include/sepol/policydb/policydb.h b/libsepol/include/sepol/policydb/policydb.h
index b014b7a8..8bb11d18 100644
--- a/libsepol/include/sepol/policydb/policydb.h
+++ b/libsepol/include/sepol/policydb/policydb.h
@@ -161,19 +161,6 @@ typedef struct role_allow {
 	struct role_allow *next;
 } role_allow_t;
 
-/* filename_trans rules */
-typedef struct filename_trans_key {
-	uint32_t ttype;
-	uint32_t tclass;
-	char *name;
-} filename_trans_key_t;
-
-typedef struct filename_trans_datum {
-	ebitmap_t stypes;
-	uint32_t otype;
-	struct filename_trans_datum *next;
-} filename_trans_datum_t;
-
 /* Type attributes */
 typedef struct type_datum {
 	symtab_datum_t s;
@@ -592,10 +579,6 @@ typedef struct policydb {
 	/* range transitions table (range_trans_key -> mls_range) */
 	hashtab_t range_tr;
 
-	/* file transitions with the last path component */
-	hashtab_t filename_trans;
-	uint32_t filename_trans_count;
-
 	ebitmap_t *type_attr_map;
 
 	ebitmap_t *attr_type_map;	/* not saved in the binary policy */
@@ -654,11 +637,6 @@ extern int policydb_load_isids(policydb_t * p, sidtab_t * s);
 
 extern int policydb_sort_ocontexts(policydb_t *p);
 
-extern int policydb_filetrans_insert(policydb_t *p, uint32_t stype,
-				     uint32_t ttype, uint32_t tclass,
-				     const char *name, char **name_alloc,
-				     uint32_t otype, uint32_t *present_otype);
-
 /* Deprecated */
 extern int policydb_context_isvalid(const policydb_t * p,
 				    const context_struct_t * c);
diff --git a/libsepol/src/avtab.c b/libsepol/src/avtab.c
index 4c292e8b..eef259cf 100644
--- a/libsepol/src/avtab.c
+++ b/libsepol/src/avtab.c
@@ -315,6 +315,20 @@ avtab_ptr_t avtab_search_node_next(avtab_ptr_t node, int specified)
 	return NULL;
 }
 
+static int avtab_trans_destroy_helper(hashtab_key_t k, hashtab_datum_t d,
+				      void *a __attribute__ ((unused)))
+{
+	free(k);
+	free(d);
+	return 0;
+}
+
+void avtab_trans_destroy(avtab_trans_t *trans)
+{
+	hashtab_map(trans->name_trans.table, avtab_trans_destroy_helper, NULL);
+	symtab_destroy(&trans->name_trans);
+}
+
 void avtab_destroy(avtab_t * h)
 {
 	unsigned int i;
@@ -329,6 +343,7 @@ void avtab_destroy(avtab_t * h)
 			if (cur->key.specified & AVTAB_XPERMS) {
 				free(cur->datum.xperms);
 			} else if (cur->key.specified & AVTAB_TRANSITION) {
+				avtab_trans_destroy(cur->datum.trans);
 				free(cur->datum.trans);
 			}
 			temp = cur;
@@ -660,3 +675,187 @@ int avtab_read(avtab_t * a, struct policy_file *fp, uint32_t vers)
 	avtab_destroy(a);
 	return -1;
 }
+
+/* policydb filename transition compatibility */
+
+int avtab_insert_filename_trans(avtab_t *a, avtab_key_t *key,
+				uint32_t otype, const char *name,
+				uint32_t *present_otype)
+{
+	int rc = SEPOL_ENOMEM;
+	avtab_trans_t new_trans = {0};
+	avtab_datum_t new_datum = {.trans = &new_trans};
+	avtab_datum_t *datum;
+	avtab_ptr_t node;
+	char *name_key = NULL;
+	uint32_t *otype_datum = NULL;
+
+	datum = avtab_search(a, key);
+	if (!datum) {
+		/*
+		 * insert is actually unique, but with this function we can get
+		 * the inserted node and therefore the datum
+		 */
+		node = avtab_insert_nonunique(a, key, &new_datum);
+		if (!node)
+			return SEPOL_ENOMEM;
+		datum = &node->datum;
+	}
+
+	if (!datum->trans->name_trans.table) {
+		rc = symtab_init(&datum->trans->name_trans, 1 << 8);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = SEPOL_ENOMEM;
+	name_key = strdup(name);
+	if (!name_key)
+		goto bad;
+
+	rc = SEPOL_ENOMEM;
+	otype_datum = malloc(sizeof(*otype_datum));
+	if (!otype_datum)
+		goto bad;
+	*otype_datum = otype;
+
+	rc = hashtab_insert(datum->trans->name_trans.table, name_key,
+			    otype_datum);
+	if (rc < 0)
+		goto bad;
+
+	return SEPOL_OK;
+
+bad:
+	free(name_key);
+	free(otype_datum);
+	if (rc == SEPOL_EEXIST && present_otype) {
+		otype_datum = hashtab_search(datum->trans->name_trans.table,
+					     name);
+		if (otype_datum)
+			*present_otype = *otype_datum;
+	}
+	return rc;
+}
+
+static int filename_trans_read_one(avtab_t *a, void *fp)
+{
+	int rc;
+	uint32_t buf[4], len, otype;
+	char *name = NULL;
+	avtab_key_t key;
+
+	/* read length of the name and the name */
+	rc = next_entry(buf, fp, sizeof(uint32_t));
+	if (rc < 0)
+		return SEPOL_ERR;
+	len = le32_to_cpu(*buf);
+	rc = str_read(&name, fp, len);
+	if (rc < 0)
+		return SEPOL_ERR;
+
+	/* read stype, ttype, tclass and otype */
+	rc = next_entry(buf, fp, sizeof(uint32_t) * 4);
+	if (rc < 0)
+		goto err;
+
+	key.specified = AVTAB_TRANSITION;
+	key.source_type = le32_to_cpu(buf[0]);
+	key.target_type = le32_to_cpu(buf[1]);
+	key.target_class = le32_to_cpu(buf[2]);
+	otype = le32_to_cpu(buf[3]);
+
+	rc = avtab_insert_filename_trans(a, &key, otype, name, NULL);
+	if (rc)
+		goto err;
+
+	free(name);
+	return SEPOL_OK;
+err:
+	free(name);
+	return SEPOL_ERR;
+}
+
+static int filename_trans_comp_read_one(avtab_t *a, void *fp)
+{
+	int rc;
+	uint32_t buf[3], len, ndatum, i, bit, otype;
+	char *name = NULL;
+	avtab_key_t key;
+	ebitmap_t stypes;
+	ebitmap_node_t *node;
+
+	/* read length of the name and the name */
+	rc = next_entry(buf, fp, sizeof(uint32_t));
+	if (rc < 0)
+		return SEPOL_ERR;
+	len = le32_to_cpu(*buf);
+	rc = str_read(&name, fp, len);
+	if (rc < 0)
+		return SEPOL_ERR;
+
+	/* read ttype, tclass, ndatum */
+	rc = next_entry(buf, fp, sizeof(uint32_t) * 3);
+	if (rc < 0)
+		goto err;
+
+	key.specified = AVTAB_TRANSITION;
+	key.target_type = le32_to_cpu(buf[0]);
+	key.target_class = le32_to_cpu(buf[1]);
+
+	ndatum = le32_to_cpu(buf[2]);
+	for (i = 0; i < ndatum; i++) {
+		rc = ebitmap_read(&stypes, fp);
+		if (rc < 0)
+			goto err;
+
+		rc = next_entry(buf, fp, sizeof(uint32_t));
+		if (rc < 0)
+			goto err_ebitmap;
+		otype = le32_to_cpu(*buf);
+
+		ebitmap_for_each_positive_bit(&stypes, node, bit) {
+			key.source_type = bit + 1;
+
+			rc = avtab_insert_filename_trans(a, &key, otype, name,
+				NULL);
+			if (rc < 0)
+				goto err_ebitmap;
+		}
+	}
+
+	free(name);
+	return SEPOL_OK;
+
+err_ebitmap:
+	ebitmap_destroy(&stypes);
+err:
+	free(name);
+	return rc;
+}
+
+int avtab_filename_trans_read(void *fp, uint32_t vers, avtab_t *a)
+{
+	uint32_t buf[1], nel, i;
+	int rc;
+
+	rc = next_entry(buf, fp, sizeof(uint32_t));
+	if (rc < 0)
+		return rc;
+	nel = le32_to_cpu(*buf);
+
+	if (vers < POLICYDB_VERSION_COMP_FTRANS) {
+		for (i = 0; i < nel; i++) {
+			rc = filename_trans_read_one(a, fp);
+			if (rc < 0)
+				return rc;
+		}
+	} else {
+		for (i = 0; i < nel; i++) {
+			rc = filename_trans_comp_read_one(a, fp);
+			if (rc < 0)
+				return rc;
+		}
+	}
+	return SEPOL_OK;
+}
diff --git a/libsepol/src/conditional.c b/libsepol/src/conditional.c
index 24380ea0..7900e928 100644
--- a/libsepol/src/conditional.c
+++ b/libsepol/src/conditional.c
@@ -624,6 +624,7 @@ static int cond_insertf(avtab_t * a
 	struct policydb *p = data->p;
 	cond_av_list_t *other = data->other, *list, *cur;
 	avtab_ptr_t node_ptr;
+	avtab_datum_t *existing;
 	uint8_t found;
 
 	/*
@@ -632,7 +633,10 @@ static int cond_insertf(avtab_t * a
 	 * cond_te_avtab.
 	 */
 	if (k->specified & AVTAB_TYPE) {
-		if (avtab_search(&p->te_avtab, k)) {
+		existing = avtab_search(&p->te_avtab, k);
+		/* empty transition rule is not a conflict */
+		if (existing && !(k->specified & AVTAB_TRANSITION
+		    && !existing->trans->otype)) {
 			WARN(NULL, "security: type rule already exists outside of a conditional.");
 			return -1;
 		}
diff --git a/libsepol/src/expand.c b/libsepol/src/expand.c
index 6793a27d..878b0f21 100644
--- a/libsepol/src/expand.c
+++ b/libsepol/src/expand.c
@@ -1413,14 +1413,17 @@ static int expand_filename_trans_helper(expand_state_t *state,
 {
 	uint32_t mapped_otype, present_otype;
 	int rc;
+	avtab_key_t avt_key;
 
 	mapped_otype = state->typemap[rule->otype - 1];
 
-	rc = policydb_filetrans_insert(
-		state->out, s + 1, t + 1,
-		rule->tclass, rule->name,
-		NULL, mapped_otype, &present_otype
-	);
+	avt_key.specified = AVTAB_TRANSITION;
+	avt_key.source_type = s + 1;
+	avt_key.target_type = t + 1;
+	avt_key.target_class = rule->tclass;
+
+	rc = avtab_insert_filename_trans(&state->out->te_avtab, &avt_key,
+		mapped_otype, rule->name, &present_otype);
 	if (rc == SEPOL_EEXIST) {
 		/* duplicate rule, ignore */
 		if (present_otype == mapped_otype)
@@ -1734,6 +1737,16 @@ static int expand_terule_helper(sepol_handle_t * handle,
 		 * either in the global scope or in another
 		 * conditional AV tab */
 		node = avtab_search_node(&p->te_avtab, &avkey);
+
+		/*
+		 * if node does not already contain transition, it is not a
+		 * conflict and transition otype will be set to node found by
+		 * find_avtab_node()
+		 */
+		if (specified & AVRULE_TRANSITION && node &&
+		    !node->datum.trans->otype)
+			node = NULL;
+
 		if (node) {
 			conflict = 1;
 		} else {
@@ -1741,6 +1754,10 @@ static int expand_terule_helper(sepol_handle_t * handle,
 			if (node && node->parse_context != other) {
 				conflict = 2;
 			}
+			/*
+			 * conditional avtab does not contain filename
+			 * transitions, no need to check for otype == 0
+			 */
 		}
 
 		if (conflict) {
diff --git a/libsepol/src/kernel_to_cil.c b/libsepol/src/kernel_to_cil.c
index b1fd1bf7..e829e235 100644
--- a/libsepol/src/kernel_to_cil.c
+++ b/libsepol/src/kernel_to_cil.c
@@ -1702,14 +1702,24 @@ static char *xperms_to_str(avtab_extended_perms_t *xperms)
 	return xpermsbuf;
 }
 
-static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_datum_t *datum)
+static int name_trans_to_strs_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
 {
-	uint32_t data = key->specified & AVTAB_TRANSITION
-		? datum->trans->otype : datum->data;
+	char *name = k;
+	uint32_t *otype = d;
+	name_trans_to_strs_args_t *args = a;
+	return strs_create_and_add(args->strs, "(%s %s %s %s \"%s\" %s)", 6,
+				   args->flavor, args->src, args->tgt,
+				   args->class, name,
+				   args->pdb->p_type_val_to_name[*otype - 1]);
+}
+
+static int avtab_node_to_strs(struct policydb *pdb, avtab_key_t *key, avtab_datum_t *datum, struct strs *strs)
+{
+	int rc = SEPOL_OK;
+	uint32_t data = datum->data;
 	type_datum_t *type;
 	const char *flavor, *tgt;
 	char *src, *class, *perms, *new;
-	char *rule = NULL;
 
 	switch (0xFFF & key->specified) {
 	case AVTAB_ALLOWED:
@@ -1742,7 +1752,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 		break;
 	default:
 		ERR(NULL, "Unknown avtab type: %i", key->specified);
-		goto exit;
+		return SEPOL_ERR;
 	}
 
 	src = pdb->p_type_val_to_name[key->source_type - 1];
@@ -1759,33 +1769,44 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 		perms = sepol_av_to_string(pdb, key->target_class, data);
 		if (perms == NULL) {
 			ERR(NULL, "Failed to generate permission string");
-			goto exit;
+			return SEPOL_ERR;
 		}
-		rule = create_str("(%s %s %s (%s (%s)))", 5,
-				  flavor, src, tgt, class, perms+1);
+		rc = strs_create_and_add(strs, "(%s %s %s (%s (%s)))", 5,
+					 flavor, src, tgt, class, perms + 1);
 	} else if (key->specified & AVTAB_XPERMS) {
 		perms = xperms_to_str(datum->xperms);
 		if (perms == NULL) {
 			ERR(NULL, "Failed to generate extended permission string");
-			goto exit;
+			return SEPOL_ERR;
 		}
 
-		rule = create_str("(%s %s %s (%s %s (%s)))", 6,
-				  flavor, src, tgt, "ioctl", class, perms);
+		rc = strs_create_and_add(strs, "(%s %s %s (%s %s (%s)))", 6,
+					 flavor, src, tgt, "ioctl", class, perms);
+	} else if (key->specified & AVTAB_TRANSITION) {
+		if (datum->trans->otype) {
+			rc = strs_create_and_add(strs, "(%s %s %s %s %s)", 5,
+						 flavor, src, tgt, class,
+						 pdb->p_type_val_to_name[datum->trans->otype - 1]);
+			if (rc < 0)
+				return rc;
+		}
+		name_trans_to_strs_args_t args = {
+			.pdb = pdb,
+			.strs = strs,
+			.flavor = flavor,
+			.src = src,
+			.tgt = tgt,
+			.class = class,
+		};
+		rc = hashtab_map(datum->trans->name_trans.table,
+				 name_trans_to_strs_helper, &args);
 	} else {
 		new = pdb->p_type_val_to_name[data - 1];
 
-		rule = create_str("(%s %s %s %s %s)", 5, flavor, src, tgt, class, new);
-	}
-
-	if (!rule) {
-		goto exit;
+		rc = strs_create_and_add(strs, "(%s %s %s %s %s)", 5, flavor, src, tgt, class, new);
 	}
 
-	return rule;
-
-exit:
-	return NULL;
+	return rc;
 }
 
 struct map_avtab_args {
@@ -1800,23 +1821,12 @@ static int map_avtab_write_helper(avtab_key_t *key, avtab_datum_t *datum, void *
 	uint32_t flavor = map_args->flavor;
 	struct policydb *pdb = map_args->pdb;
 	struct strs *strs = map_args->strs;
-	char *rule;
 	int rc = 0;
 
 	if (key->specified & flavor) {
-		rule = avtab_node_to_str(pdb, key, datum);
-		if (!rule) {
-			rc = -1;
-			goto exit;
-		}
-		rc = strs_add(strs, rule);
-		if (rc != 0) {
-			free(rule);
-			goto exit;
-		}
+		rc = avtab_node_to_strs(pdb, key, datum, strs);
 	}
 
-exit:
 	return rc;
 }
 
@@ -1870,77 +1880,6 @@ exit:
 	return rc;
 }
 
-struct map_filename_trans_args {
-	struct policydb *pdb;
-	struct strs *strs;
-};
-
-static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
-{
-	filename_trans_key_t *ft = (filename_trans_key_t *)key;
-	filename_trans_datum_t *datum = data;
-	struct map_filename_trans_args *map_args = arg;
-	struct policydb *pdb = map_args->pdb;
-	struct strs *strs = map_args->strs;
-	char *src, *tgt, *class, *filename, *new;
-	struct ebitmap_node *node;
-	uint32_t bit;
-	int rc;
-
-	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
-	class = pdb->p_class_val_to_name[ft->tclass - 1];
-	filename = ft->name;
-	do {
-		new = pdb->p_type_val_to_name[datum->otype - 1];
-
-		ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
-			src = pdb->p_type_val_to_name[bit];
-			rc = strs_create_and_add(strs,
-						 "(typetransition %s %s %s \"%s\" %s)",
-						 5, src, tgt, class, filename, new);
-			if (rc)
-				return rc;
-		}
-
-		datum = datum->next;
-	} while (datum);
-
-	return 0;
-}
-
-static int write_filename_trans_rules_to_cil(FILE *out, struct policydb *pdb)
-{
-	struct map_filename_trans_args args;
-	struct strs *strs;
-	int rc = 0;
-
-	rc = strs_init(&strs, 100);
-	if (rc != 0) {
-		goto exit;
-	}
-
-	args.pdb = pdb;
-	args.strs = strs;
-
-	rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
-	if (rc != 0) {
-		goto exit;
-	}
-
-	strs_sort(strs);
-	strs_write_each(strs, out);
-
-exit:
-	strs_free_all(strs);
-	strs_destroy(&strs);
-
-	if (rc != 0) {
-		ERR(NULL, "Error writing filename typetransition rules to CIL");
-	}
-
-	return rc;
-}
-
 static char *level_to_str(struct policydb *pdb, struct mls_level *level)
 {
 	ebitmap_t *cats = &level->cat;
@@ -2060,7 +1999,6 @@ static int write_cond_av_list_to_cil(FILE *out, struct policydb *pdb, cond_av_li
 	avtab_key_t *key;
 	avtab_datum_t *datum;
 	struct strs *strs;
-	char *rule;
 	unsigned i;
 	int rc;
 
@@ -2076,14 +2014,8 @@ static int write_cond_av_list_to_cil(FILE *out, struct policydb *pdb, cond_av_li
 			key = &node->key;
 			datum = &node->datum;
 			if (key->specified & flavor) {
-				rule = avtab_node_to_str(pdb, key, datum);
-				if (!rule) {
-					rc = -1;
-					goto exit;
-				}
-				rc = strs_add(strs, rule);
+				rc = avtab_node_to_strs(pdb, key, datum, strs);
 				if (rc != 0) {
-					free(rule);
 					goto exit;
 				}
 			}
@@ -3331,11 +3263,6 @@ int sepol_kernel_policydb_to_cil(FILE *out, struct policydb *pdb)
 		goto exit;
 	}
 
-	rc = write_filename_trans_rules_to_cil(out, pdb);
-	if (rc != 0) {
-		goto exit;
-	}
-
 	if (pdb->mls) {
 		rc = write_range_trans_rules_to_cil(out, pdb);
 		if (rc != 0) {
diff --git a/libsepol/src/kernel_to_common.h b/libsepol/src/kernel_to_common.h
index 7a3960b0..524a4b0f 100644
--- a/libsepol/src/kernel_to_common.h
+++ b/libsepol/src/kernel_to_common.h
@@ -83,6 +83,15 @@ struct strs {
 	size_t size;
 };
 
+typedef struct {
+	policydb_t *pdb;
+	struct strs *strs;
+	const char *flavor;
+	const char *src;
+	const char *tgt;
+	const char *class;
+} name_trans_to_strs_args_t;
+
 void sepol_indent(FILE *out, int indent);
 __attribute__ ((format(printf, 2, 3)))
 void sepol_printf(FILE *out, const char *fmt, ...);
diff --git a/libsepol/src/kernel_to_conf.c b/libsepol/src/kernel_to_conf.c
index 7e1e1b49..a637c207 100644
--- a/libsepol/src/kernel_to_conf.c
+++ b/libsepol/src/kernel_to_conf.c
@@ -1680,13 +1680,24 @@ exit:
 	return rc;
 }
 
-static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_datum_t *datum)
+static int name_trans_to_strs_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
 {
-	uint32_t data = key->specified & AVTAB_TRANSITION
-		? datum->trans->otype : datum->data;
+	char *name = k;
+	uint32_t *otype = d;
+	name_trans_to_strs_args_t *args = a;
+	return strs_create_and_add(args->strs, "%s %s %s:%s %s \"%s\";", 6,
+				   args->flavor, args->src, args->tgt,
+				   args->class,
+				   args->pdb->p_type_val_to_name[*otype - 1],
+				   name);
+}
+
+static int avtab_node_to_strs(struct policydb *pdb, avtab_key_t *key, avtab_datum_t *datum, struct strs *strs)
+{
+	int rc = SEPOL_OK;
+	uint32_t data = datum->data;
 	type_datum_t *type;
 	const char *flavor, *src, *tgt, *class, *perms, *new;
-	char *rule = NULL;
 
 	switch (0xFFF & key->specified) {
 	case AVTAB_ALLOWED:
@@ -1719,7 +1730,7 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 		break;
 	default:
 		ERR(NULL, "Unknown avtab type: %i", key->specified);
-		goto exit;
+		return SEPOL_ERR;
 	}
 
 	src = pdb->p_type_val_to_name[key->source_type - 1];
@@ -1736,32 +1747,42 @@ static char *avtab_node_to_str(struct policydb *pdb, avtab_key_t *key, avtab_dat
 		perms = sepol_av_to_string(pdb, key->target_class, data);
 		if (perms == NULL) {
 			ERR(NULL, "Failed to generate permission string");
-			goto exit;
+			return SEPOL_ERR;
 		}
-		rule = create_str("%s %s %s:%s { %s };", 5,
-				  flavor, src, tgt, class, perms+1);
+		rc = strs_create_and_add(strs, "%s %s %s:%s { %s };", 5,
+					 flavor, src, tgt, class, perms + 1);
 	} else if (key->specified & AVTAB_XPERMS) {
 		perms = sepol_extended_perms_to_string(datum->xperms);
 		if (perms == NULL) {
 			ERR(NULL, "Failed to generate extended permission string");
-			goto exit;
+			return SEPOL_ERR;
+		}
+		rc = strs_create_and_add(strs, "%s %s %s:%s %s;", 5, flavor, src, tgt, class, perms);
+	} else if (key->specified & AVTAB_TRANSITION) {
+		if (datum->trans->otype) {
+			rc = strs_create_and_add(strs, "%s %s %s:%s %s;", 5,
+						 flavor, src, tgt, class,
+						 pdb->p_type_val_to_name[datum->trans->otype - 1]);
+			if (rc < 0)
+				return rc;
 		}
-
-		rule = create_str("%s %s %s:%s %s;", 5, flavor, src, tgt, class, perms);
+		name_trans_to_strs_args_t args = {
+			.pdb = pdb,
+			.strs = strs,
+			.flavor = flavor,
+			.src = src,
+			.tgt = tgt,
+			.class = class,
+		};
+		rc = hashtab_map(datum->trans->name_trans.table,
+				 name_trans_to_strs_helper, &args);
 	} else {
 		new = pdb->p_type_val_to_name[data - 1];
 
-		rule = create_str("%s %s %s:%s %s;", 5, flavor, src, tgt, class, new);
+		rc = strs_create_and_add(strs, "%s %s %s:%s %s;", 5, flavor, src, tgt, class, new);
 	}
 
-	if (!rule) {
-		goto exit;
-	}
-
-	return rule;
-
-exit:
-	return NULL;
+	return rc;
 }
 
 struct map_avtab_args {
@@ -1776,23 +1797,12 @@ static int map_avtab_write_helper(avtab_key_t *key, avtab_datum_t *datum, void *
 	uint32_t flavor = map_args->flavor;
 	struct policydb *pdb = map_args->pdb;
 	struct strs *strs = map_args->strs;
-	char *rule;
 	int rc = 0;
 
 	if (key->specified & flavor) {
-		rule = avtab_node_to_str(pdb, key, datum);
-		if (!rule) {
-			rc = -1;
-			goto exit;
-		}
-		rc = strs_add(strs, rule);
-		if (rc != 0) {
-			free(rule);
-			goto exit;
-		}
+		rc = avtab_node_to_strs(pdb, key, datum, strs);
 	}
 
-exit:
 	return rc;
 }
 
@@ -1846,77 +1856,6 @@ exit:
 	return rc;
 }
 
-struct map_filename_trans_args {
-	struct policydb *pdb;
-	struct strs *strs;
-};
-
-static int map_filename_trans_to_str(hashtab_key_t key, void *data, void *arg)
-{
-	filename_trans_key_t *ft = (filename_trans_key_t *)key;
-	filename_trans_datum_t *datum = data;
-	struct map_filename_trans_args *map_args = arg;
-	struct policydb *pdb = map_args->pdb;
-	struct strs *strs = map_args->strs;
-	char *src, *tgt, *class, *filename, *new;
-	struct ebitmap_node *node;
-	uint32_t bit;
-	int rc;
-
-	tgt = pdb->p_type_val_to_name[ft->ttype - 1];
-	class = pdb->p_class_val_to_name[ft->tclass - 1];
-	filename = ft->name;
-	do {
-		new = pdb->p_type_val_to_name[datum->otype - 1];
-
-		ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
-			src = pdb->p_type_val_to_name[bit];
-			rc = strs_create_and_add(strs,
-						 "type_transition %s %s:%s %s \"%s\";",
-						 5, src, tgt, class, new, filename);
-			if (rc)
-				return rc;
-		}
-
-		datum = datum->next;
-	} while (datum);
-
-	return 0;
-}
-
-static int write_filename_trans_rules_to_conf(FILE *out, struct policydb *pdb)
-{
-	struct map_filename_trans_args args;
-	struct strs *strs;
-	int rc = 0;
-
-	rc = strs_init(&strs, 100);
-	if (rc != 0) {
-		goto exit;
-	}
-
-	args.pdb = pdb;
-	args.strs = strs;
-
-	rc = hashtab_map(pdb->filename_trans, map_filename_trans_to_str, &args);
-	if (rc != 0) {
-		goto exit;
-	}
-
-	strs_sort(strs);
-	strs_write_each(strs, out);
-
-exit:
-	strs_free_all(strs);
-	strs_destroy(&strs);
-
-	if (rc != 0) {
-		ERR(NULL, "Error writing filename typetransition rules to policy.conf");
-	}
-
-	return rc;
-}
-
 static char *level_to_str(struct policydb *pdb, struct mls_level *level)
 {
 	ebitmap_t *cats = &level->cat;
@@ -2036,7 +1975,6 @@ static int write_cond_av_list_to_conf(FILE *out, struct policydb *pdb, cond_av_l
 	avtab_key_t *key;
 	avtab_datum_t *datum;
 	struct strs *strs;
-	char *rule;
 	unsigned i;
 	int rc;
 
@@ -2052,14 +1990,8 @@ static int write_cond_av_list_to_conf(FILE *out, struct policydb *pdb, cond_av_l
 			key = &node->key;
 			datum = &node->datum;
 			if (key->specified & flavor) {
-				rule = avtab_node_to_str(pdb, key, datum);
-				if (!rule) {
-					rc = -1;
-					goto exit;
-				}
-				rc = strs_add(strs, rule);
+				rc = avtab_node_to_strs(pdb, key, datum, strs);
 				if (rc != 0) {
-					free(rule);
 					goto exit;
 				}
 			}
@@ -3205,7 +3137,6 @@ int sepol_kernel_policydb_to_conf(FILE *out, struct policydb *pdb)
 	if (rc != 0) {
 		goto exit;
 	}
-	write_filename_trans_rules_to_conf(out, pdb);
 
 	if (pdb->mls) {
 		rc = write_range_trans_rules_to_conf(out, pdb);
diff --git a/libsepol/src/optimize.c b/libsepol/src/optimize.c
index 2d4a2d7a..7948d983 100644
--- a/libsepol/src/optimize.c
+++ b/libsepol/src/optimize.c
@@ -308,8 +308,10 @@ static void optimize_avtab(policydb_t *p, const struct type_vec *type_map)
 				*cur = tmp->next;
 				if (tmp->key.specified & AVTAB_XPERMS)
 					free(tmp->datum.xperms);
-				if (tmp->key.specified & AVTAB_TRANSITION)
+				if (tmp->key.specified & AVTAB_TRANSITION) {
+					avtab_trans_destroy(tmp->datum.trans);
 					free(tmp->datum.trans);
+				}
 				free(tmp);
 
 				tab->nel--;
@@ -429,8 +431,10 @@ static void optimize_cond_avtab(policydb_t *p, const struct type_vec *type_map)
 				*cur = tmp->next;
 				if (tmp->key.specified & AVTAB_XPERMS)
 					free(tmp->datum.xperms);
-				if (tmp->key.specified & AVTAB_TRANSITION)
+				if (tmp->key.specified & AVTAB_TRANSITION) {
+					avtab_trans_destroy(tmp->datum.trans);
 					free(tmp->datum.trans);
+				}
 				free(tmp);
 
 				tab->nel--;
diff --git a/libsepol/src/policydb.c b/libsepol/src/policydb.c
index 605d290a..c1ce9c34 100644
--- a/libsepol/src/policydb.c
+++ b/libsepol/src/policydb.c
@@ -801,47 +801,6 @@ static int roles_init(policydb_t * p)
 	goto out;
 }
 
-ignore_unsigned_overflow_
-static inline unsigned long
-partial_name_hash(unsigned long c, unsigned long prevhash)
-{
-	return (prevhash + (c << 4) + (c >> 4)) * 11;
-}
-
-static unsigned int filenametr_hash(hashtab_t h, const_hashtab_key_t k)
-{
-	const filename_trans_key_t *ft = (const filename_trans_key_t *)k;
-	unsigned long hash;
-	unsigned int byte_num;
-	unsigned char focus;
-
-	hash = ft->ttype ^ ft->tclass;
-
-	byte_num = 0;
-	while ((focus = ft->name[byte_num++]))
-		hash = partial_name_hash(focus, hash);
-	return hash & (h->size - 1);
-}
-
-static int filenametr_cmp(hashtab_t h __attribute__ ((unused)),
-			  const_hashtab_key_t k1, const_hashtab_key_t k2)
-{
-	const filename_trans_key_t *ft1 = (const filename_trans_key_t *)k1;
-	const filename_trans_key_t *ft2 = (const filename_trans_key_t *)k2;
-	int v;
-
-	v = spaceship_cmp(ft1->ttype, ft2->ttype);
-	if (v)
-		return v;
-
-	v = spaceship_cmp(ft1->tclass, ft2->tclass);
-	if (v)
-		return v;
-
-	return strcmp(ft1->name, ft2->name);
-
-}
-
 static unsigned int rangetr_hash(hashtab_t h, const_hashtab_key_t k)
 {
 	const struct range_trans *key = (const struct range_trans *)k;
@@ -909,12 +868,6 @@ int policydb_init(policydb_t * p)
 	if (rc)
 		goto err;
 
-	p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10));
-	if (!p->filename_trans) {
-		rc = -ENOMEM;
-		goto err;
-	}
-
 	p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256);
 	if (!p->range_tr) {
 		rc = -ENOMEM;
@@ -926,7 +879,6 @@ int policydb_init(policydb_t * p)
 
 	return 0;
 err:
-	hashtab_destroy(p->filename_trans);
 	hashtab_destroy(p->range_tr);
 	for (i = 0; i < SYM_NUM; i++) {
 		hashtab_destroy(p->symtab[i].table);
@@ -1412,23 +1364,6 @@ static int (*destroy_f[SYM_NUM]) (hashtab_key_t key, hashtab_datum_t datum,
 common_destroy, class_destroy, role_destroy, type_destroy, user_destroy,
 	    cond_destroy_bool, sens_destroy, cat_destroy,};
 
-static int filenametr_destroy(hashtab_key_t key, hashtab_datum_t datum,
-			      void *p __attribute__ ((unused)))
-{
-	filename_trans_key_t *ft = (filename_trans_key_t *)key;
-	filename_trans_datum_t *fd = datum, *next;
-
-	free(ft->name);
-	free(key);
-	do {
-		next = fd->next;
-		ebitmap_destroy(&fd->stypes);
-		free(fd);
-		fd = next;
-	} while (fd);
-	return 0;
-}
-
 static int range_tr_destroy(hashtab_key_t key, hashtab_datum_t datum,
 			    void *p __attribute__ ((unused)))
 {
@@ -1564,9 +1499,6 @@ void policydb_destroy(policydb_t * p)
 	if (lra)
 		free(lra);
 
-	hashtab_map(p->filename_trans, filenametr_destroy, NULL);
-	hashtab_destroy(p->filename_trans);
-
 	hashtab_map(p->range_tr, range_tr_destroy, NULL);
 	hashtab_destroy(p->range_tr);
 
@@ -2596,279 +2528,6 @@ static int role_allow_read(role_allow_t ** r, struct policy_file *fp)
 	return 0;
 }
 
-int policydb_filetrans_insert(policydb_t *p, uint32_t stype, uint32_t ttype,
-			      uint32_t tclass, const char *name,
-			      char **name_alloc, uint32_t otype,
-			      uint32_t *present_otype)
-{
-	filename_trans_key_t *ft, key;
-	filename_trans_datum_t *datum, *last;
-
-	key.ttype = ttype;
-	key.tclass = tclass;
-	key.name = (char *)name;
-
-	last = NULL;
-	datum = hashtab_search(p->filename_trans, (hashtab_key_t)&key);
-	while (datum) {
-		if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
-			if (present_otype)
-				*present_otype = datum->otype;
-			return SEPOL_EEXIST;
-		}
-		if (datum->otype == otype)
-			break;
-		last = datum;
-		datum = datum->next;
-	}
-	if (!datum) {
-		datum = malloc(sizeof(*datum));
-		if (!datum)
-			return SEPOL_ENOMEM;
-
-		ebitmap_init(&datum->stypes);
-		datum->otype = otype;
-		datum->next = NULL;
-
-		if (last) {
-			last->next = datum;
-		} else {
-			char *name_dup;
-
-			if (name_alloc) {
-				name_dup = *name_alloc;
-				*name_alloc = NULL;
-			} else {
-				name_dup = strdup(name);
-				if (!name_dup) {
-					free(datum);
-					return SEPOL_ENOMEM;
-				}
-			}
-
-			ft = malloc(sizeof(*ft));
-			if (!ft) {
-				free(name_dup);
-				free(datum);
-				return SEPOL_ENOMEM;
-			}
-
-			ft->ttype = ttype;
-			ft->tclass = tclass;
-			ft->name = name_dup;
-
-			if (hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
-					   (hashtab_datum_t)datum)) {
-				free(name_dup);
-				free(datum);
-				free(ft);
-				return SEPOL_ENOMEM;
-			}
-		}
-	}
-
-	p->filename_trans_count++;
-	return ebitmap_set_bit(&datum->stypes, stype - 1, 1);
-}
-
-static int filename_trans_read_one_compat(policydb_t *p, struct policy_file *fp)
-{
-	uint32_t buf[4], len, stype, ttype, tclass, otype;
-	char *name = NULL;
-	int rc;
-
-	rc = next_entry(buf, fp, sizeof(uint32_t));
-	if (rc < 0)
-		return -1;
-	len = le32_to_cpu(buf[0]);
-	if (zero_or_saturated(len))
-		return -1;
-
-	name = calloc(len + 1, sizeof(*name));
-	if (!name)
-		return -1;
-
-	rc = next_entry(name, fp, len);
-	if (rc < 0)
-		goto err;
-
-	rc = next_entry(buf, fp, sizeof(uint32_t) * 4);
-	if (rc < 0)
-		goto err;
-
-	stype = le32_to_cpu(buf[0]);
-	if (stype == 0)
-		goto err;
-
-	ttype  = le32_to_cpu(buf[1]);
-	tclass = le32_to_cpu(buf[2]);
-	otype  = le32_to_cpu(buf[3]);
-
-	rc = policydb_filetrans_insert(p, stype, ttype, tclass, name, &name,
-				       otype, NULL);
-	if (rc) {
-		if (rc != SEPOL_EEXIST)
-			goto err;
-		/*
-		 * Some old policies were wrongly generated with
-		 * duplicate filename transition rules.  For backward
-		 * compatibility, do not reject such policies, just
-		 * ignore the duplicate.
-		 */
-	}
-	free(name);
-	return 0;
-err:
-	free(name);
-	return -1;
-}
-
-static int filename_trans_check_datum(filename_trans_datum_t *datum)
-{
-	ebitmap_t stypes, otypes;
-	int rc = -1;
-
-	ebitmap_init(&stypes);
-	ebitmap_init(&otypes);
-
-	while (datum) {
-		if (ebitmap_get_bit(&otypes, datum->otype))
-			goto out;
-
-		if (ebitmap_set_bit(&otypes, datum->otype, 1))
-			goto out;
-
-		if (ebitmap_match_any(&stypes, &datum->stypes))
-			goto out;
-
-		if (ebitmap_union(&stypes, &datum->stypes))
-			goto out;
-
-		datum = datum->next;
-	}
-	rc = 0;
-out:
-	ebitmap_destroy(&stypes);
-	ebitmap_destroy(&otypes);
-	return rc;
-}
-
-static int filename_trans_read_one(policydb_t *p, struct policy_file *fp)
-{
-	filename_trans_key_t *ft = NULL;
-	filename_trans_datum_t **dst, *datum, *first = NULL;
-	unsigned int i;
-	uint32_t buf[3], len, ttype, tclass, ndatum;
-	char *name = NULL;
-	int rc;
-
-	rc = next_entry(buf, fp, sizeof(uint32_t));
-	if (rc < 0)
-		return -1;
-	len = le32_to_cpu(buf[0]);
-	if (zero_or_saturated(len))
-		return -1;
-
-	name = calloc(len + 1, sizeof(*name));
-	if (!name)
-		return -1;
-
-	rc = next_entry(name, fp, len);
-	if (rc < 0)
-		goto err;
-
-	rc = next_entry(buf, fp, sizeof(uint32_t) * 3);
-	if (rc < 0)
-		goto err;
-
-	ttype = le32_to_cpu(buf[0]);
-	tclass = le32_to_cpu(buf[1]);
-	ndatum = le32_to_cpu(buf[2]);
-	if (ndatum == 0)
-		goto err;
-
-	dst = &first;
-	for (i = 0; i < ndatum; i++) {
-		datum = malloc(sizeof(*datum));
-		if (!datum)
-			goto err;
-
-		datum->next = NULL;
-		*dst = datum;
-
-		/* ebitmap_read() will at least init the bitmap */
-		rc = ebitmap_read(&datum->stypes, fp);
-		if (rc < 0)
-			goto err;
-
-		rc = next_entry(buf, fp, sizeof(uint32_t));
-		if (rc < 0)
-			goto err;
-
-		datum->otype = le32_to_cpu(buf[0]);
-
-		p->filename_trans_count += ebitmap_cardinality(&datum->stypes);
-
-		dst = &datum->next;
-	}
-
-	if (ndatum > 1 && filename_trans_check_datum(first))
-		goto err;
-
-	ft = malloc(sizeof(*ft));
-	if (!ft)
-		goto err;
-
-	ft->ttype = ttype;
-	ft->tclass = tclass;
-	ft->name = name;
-
-	rc = hashtab_insert(p->filename_trans, (hashtab_key_t)ft,
-			    (hashtab_datum_t)first);
-	if (rc)
-		goto err;
-
-	return 0;
-err:
-	free(ft);
-	free(name);
-	while (first) {
-		datum = first;
-		first = first->next;
-
-		ebitmap_destroy(&datum->stypes);
-		free(datum);
-	}
-	return -1;
-}
-
-static int filename_trans_read(policydb_t *p, struct policy_file *fp)
-{
-	unsigned int i;
-	uint32_t buf[1], nel;
-	int rc;
-
-	rc = next_entry(buf, fp, sizeof(uint32_t));
-	if (rc < 0)
-		return -1;
-	nel = le32_to_cpu(buf[0]);
-
-	if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
-		for (i = 0; i < nel; i++) {
-			rc = filename_trans_read_one_compat(p, fp);
-			if (rc < 0)
-				return -1;
-		}
-	} else {
-		for (i = 0; i < nel; i++) {
-			rc = filename_trans_read_one(p, fp);
-			if (rc < 0)
-				return -1;
-		}
-	}
-	return 0;
-}
-
 static int ocontext_read_xen(const struct policydb_compat_info *info,
 	policydb_t *p, struct policy_file *fp)
 {
@@ -4470,7 +4129,7 @@ int policydb_read(policydb_t * p, struct policy_file *fp, unsigned verbose)
 		if (role_allow_read(&p->role_allow, fp))
 			goto bad;
 		if (r_policyvers >= POLICYDB_VERSION_FILENAME_TRANS &&
-		    filename_trans_read(p, fp))
+		    avtab_filename_trans_read(fp, r_policyvers, &p->te_avtab))
 			goto bad;
 	} else {
 		/* first read the AV rule blocks, then the scope tables */
diff --git a/libsepol/src/policydb_validate.c b/libsepol/src/policydb_validate.c
index f402b506..89306185 100644
--- a/libsepol/src/policydb_validate.c
+++ b/libsepol/src/policydb_validate.c
@@ -829,6 +829,18 @@ static int validate_xperms(const avtab_extended_perms_t *xperms)
 bad:
 	return -1;
 }
+
+static int validate_name_trans_helper(hashtab_key_t k __attribute__ ((unused)),
+				      hashtab_datum_t d, void *a)
+{
+	uint32_t *otype = d;
+	map_arg_t *margs = a;
+
+	if (validate_simpletype(*otype, margs->policy, margs->flavors))
+		return -1;
+	return 0;
+}
+
 static int validate_avtab_key_and_datum(avtab_key_t *k, avtab_datum_t *d, void *args)
 {
 	map_arg_t *margs = args;
@@ -836,10 +848,23 @@ static int validate_avtab_key_and_datum(avtab_key_t *k, avtab_datum_t *d, void *
 	if (validate_avtab_key(k, 0, margs->policy, margs->flavors))
 		return -1;
 
-	uint32_t otype = k->specified & AVTAB_TRANSITION
-		? d->trans->otype : d->data;
-	if ((k->specified & AVTAB_TYPE) && validate_simpletype(otype, margs->policy, margs->flavors))
+	if (k->specified & AVTAB_TRANSITION) {
+		/* if otype is set (non-zero), it must by a valid simple type */
+		if (d->trans->otype && validate_simpletype(d->trans->otype, margs->policy, margs->flavors))
+			return -1;
+
+		/* also each transition must be non empty */
+		if (!d->trans->otype &&
+		    !hashtab_nel(d->trans->name_trans.table))
+			return -1;
+
+		/* and each filename transition must be also valid */
+		if (hashtab_map(d->trans->name_trans.table,
+				validate_name_trans_helper, margs))
+			return -1;
+	} else if ((k->specified & AVTAB_TYPE) && validate_simpletype(d->data, margs->policy, margs->flavors)) {
 		return -1;
+	}
 
 	if ((k->specified & AVTAB_XPERMS) && validate_xperms(d->xperms))
 		return -1;
@@ -1092,41 +1117,6 @@ bad:
 	return -1;
 }
 
-static int validate_filename_trans(hashtab_key_t k, hashtab_datum_t d, void *args)
-{
-	const filename_trans_key_t *ftk = (filename_trans_key_t *)k;
-	const filename_trans_datum_t *ftd = d;
-	validate_t *flavors = (validate_t *)args;
-
-	if (validate_value(ftk->ttype, &flavors[SYM_TYPES]))
-		goto bad;
-	if (validate_value(ftk->tclass, &flavors[SYM_CLASSES]))
-		goto bad;
-	if (!ftd)
-		goto bad;
-	for (; ftd; ftd = ftd->next) {
-		if (validate_ebitmap(&ftd->stypes, &flavors[SYM_TYPES]))
-			goto bad;
-		if (validate_value(ftd->otype, &flavors[SYM_TYPES]))
-			goto bad;
-	}
-
-	return 0;
-
-bad:
-	return -1;
-}
-
-static int validate_filename_trans_hashtab(sepol_handle_t *handle, hashtab_t filename_trans, validate_t flavors[])
-{
-	if (hashtab_map(filename_trans, validate_filename_trans, flavors)) {
-		ERR(handle, "Invalid filename trans");
-		return -1;
-	}
-
-	return 0;
-}
-
 static int validate_context(const context_struct_t *con, validate_t flavors[], int mls)
 {
 	if (validate_value(con->user, &flavors[SYM_USERS]))
@@ -1556,9 +1546,6 @@ int policydb_validate(sepol_handle_t *handle, const policydb_t *p)
 			goto bad;
 		if (validate_role_allows(handle, p->role_allow, flavors))
 			goto bad;
-		if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS)
-			if (validate_filename_trans_hashtab(handle, p->filename_trans, flavors))
-				goto bad;
 	} else {
 		if (validate_avrule_blocks(handle, p->global, p, flavors))
 			goto bad;
diff --git a/libsepol/src/write.c b/libsepol/src/write.c
index 0d3d5f14..c4d593ab 100644
--- a/libsepol/src/write.c
+++ b/libsepol/src/write.c
@@ -116,6 +116,14 @@ static int avtab_write_item(policydb_t * p,
 				&& p->policyvers < POLICYDB_VERSION_AVTAB);
 	unsigned int i;
 
+	/* skip entries which only contain filename transitions */
+	if (cur->key.specified & AVTAB_TRANSITION && !cur->datum.trans->otype) {
+		/* if oldvers, reduce nel, because this node will be skipped */
+		if (oldvers && nel)
+			(*nel)--;
+		return 0;
+	}
+
 	if (oldvers) {
 		/* Generate the old avtab format.
 		   Requires merging similar entries if uncond avtab. */
@@ -313,8 +321,23 @@ static int avtab_write(struct policydb *p, avtab_t * a, struct policy_file *fp)
 		avtab_reset_merged(a);
 		nel = a->nel;
 	} else {
-		/* New avtab format.  nel is good to go. */
-		nel = cpu_to_le32(a->nel);
+		/*
+		 * New avtab format.  nel is good to go unless we need to skip
+		 * filename transitions.
+		 */
+		nel = a->nel;
+		/*
+		 * entries containing only filename transitions are skipped and
+		 * written out later
+		 */
+		for (i = 0; i < a->nslot; i++) {
+			for (cur = a->htable[i]; cur; cur = cur->next) {
+				if (cur->key.specified & AVTAB_TRANSITION &&
+				    !cur->datum.trans->otype)
+					nel--;
+			}
+		}
+		nel = cpu_to_le32(nel);
 		items = put_entry(&nel, sizeof(uint32_t), 1, fp);
 		if (items != 1)
 			return POLICYDB_ERROR;
@@ -358,6 +381,307 @@ static int avtab_write(struct policydb *p, avtab_t * a, struct policy_file *fp)
 	return rc;
 }
 
+/* policydb filename transition compatibility */
+
+typedef struct filenametr_key {
+	uint32_t ttype;
+	uint32_t tclass;
+	char *name;
+} filenametr_key_t;
+
+typedef struct filenametr_datum {
+	ebitmap_t stypes;
+	uint32_t otype;
+	struct filenametr_datum *next;
+} filenametr_datum_t;
+
+ignore_unsigned_overflow_
+static inline unsigned long
+partial_name_hash(unsigned long c, unsigned long prevhash)
+{
+	return (prevhash + (c << 4) + (c >> 4)) * 11;
+}
+
+static unsigned int filenametr_hash(hashtab_t h, const_hashtab_key_t k)
+{
+	const filenametr_key_t *ft = (const filenametr_key_t *)k;
+	unsigned long hash;
+	unsigned int byte_num;
+	unsigned char focus;
+
+	hash = ft->ttype ^ ft->tclass;
+
+	byte_num = 0;
+	while ((focus = ft->name[byte_num++]))
+		hash = partial_name_hash(focus, hash);
+	return hash & (h->size - 1);
+}
+
+static int filenametr_cmp(hashtab_t h __attribute__ ((unused)),
+			  const_hashtab_key_t k1, const_hashtab_key_t k2)
+{
+	const filenametr_key_t *ft1 = (const filenametr_key_t *)k1;
+	const filenametr_key_t *ft2 = (const filenametr_key_t *)k2;
+	int v;
+
+	v = spaceship_cmp(ft1->ttype, ft2->ttype);
+	if (v)
+		return v;
+
+	v = spaceship_cmp(ft1->tclass, ft2->tclass);
+	if (v)
+		return v;
+
+	return strcmp(ft1->name, ft2->name);
+}
+
+static int filenametr_destroy(hashtab_key_t key, hashtab_datum_t datum,
+			      void *p __attribute__ ((unused)))
+{
+	filenametr_key_t *ft = (filenametr_key_t *)key;
+	filenametr_datum_t *fd = datum, *next;
+
+	free(ft->name);
+	free(key);
+	do {
+		next = fd->next;
+		ebitmap_destroy(&fd->stypes);
+		free(fd);
+		fd = next;
+	} while (fd);
+	return 0;
+}
+
+typedef struct {
+	void *fp;
+	avtab_key_t *key;
+} name_trans_write_args_t;
+
+static int name_trans_write_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
+{
+	char *name = k;
+	uint32_t *otype = d;
+	name_trans_write_args_t *args = a;
+	size_t items;
+	uint32_t len, buf[4];
+
+	len = strlen(name);
+	buf[0] = cpu_to_le32(len);
+	items = put_entry(buf, sizeof(uint32_t), 1, args->fp);
+	if (items != 1)
+		return -1;
+
+	items = put_entry(name, sizeof(char), len,args-> fp);
+	if (items != len)
+		return -1;
+
+	buf[0] = cpu_to_le32(args->key->source_type);
+	buf[1] = cpu_to_le32(args->key->target_type);
+	buf[2] = cpu_to_le32(args->key->target_class);
+	buf[3] = cpu_to_le32(*otype);
+
+	items = put_entry(buf, sizeof(uint32_t), 4, args->fp);
+	if (items != 4)
+		return -1;
+	return 0;
+}
+
+typedef struct {
+	hashtab_t fnts_tab;
+	avtab_key_t *av_key;
+} name_trans_insert_args_t;
+
+static int name_trans_insert_helper(hashtab_key_t k, hashtab_datum_t d, void *a)
+{
+	char *name = k;
+	uint32_t *otype = d;
+	name_trans_insert_args_t *args = a;
+	filenametr_key_t key, *ft = NULL;
+	filenametr_datum_t *last, *datum = NULL;
+	int rc;
+
+	key.ttype = args->av_key->target_type;
+	key.tclass = args->av_key->target_class;
+	key.name = name;
+
+	last = NULL;
+	datum = hashtab_search(args->fnts_tab, (hashtab_key_t)&key);
+	while (datum) {
+		if (ebitmap_get_bit(&datum->stypes, args->av_key->source_type - 1)) {
+			datum = NULL;
+			goto bad;
+		}
+		if (datum->otype == *otype)
+			break;
+		last = datum;
+		datum = datum->next;
+	}
+	if (!datum) {
+		datum = malloc(sizeof(filenametr_datum_t));
+		if (!datum)
+			goto bad;
+
+		ebitmap_init(&datum->stypes);
+		datum->otype = *otype;
+		datum->next = NULL;
+
+		if (last) {
+			last->next = datum;
+		} else {
+			ft = malloc(sizeof(filenametr_key_t));
+			if (!ft)
+				goto bad;
+
+			ft->ttype = args->av_key->target_type;
+			ft->tclass = args->av_key->target_class;
+			ft->name = strdup(name);
+			if (!ft->name)
+				goto bad;
+
+			rc = hashtab_insert(args->fnts_tab, (hashtab_key_t)ft, datum);
+			if (rc)
+				goto bad;
+		}
+	}
+
+	return ebitmap_set_bit(&datum->stypes, args->av_key->source_type - 1, 1);
+
+bad:
+	if (ft != NULL)
+		free(ft->name);
+	free(ft);
+	free(datum);
+	return -1;
+}
+
+static int filenametr_comp_write_one(hashtab_key_t key, void *data, void *ptr)
+{
+	uint32_t buf[3];
+	size_t items, len, ndatum;
+	filenametr_key_t *ft = (filenametr_key_t *)key;
+	filenametr_datum_t *datum;
+	void *fp = ptr;
+
+	len = strlen(ft->name);
+	buf[0] = cpu_to_le32(len);
+	items = put_entry(buf, sizeof(uint32_t), 1, fp);
+	if (items != 1)
+		return POLICYDB_ERROR;
+
+	items = put_entry(ft->name, sizeof(char), len, fp);
+	if (items != len)
+		return POLICYDB_ERROR;
+
+	ndatum = 0;
+	datum = data;
+	do {
+		ndatum++;
+		datum = datum->next;
+	} while (datum);
+
+	buf[0] = cpu_to_le32(ft->ttype);
+	buf[1] = cpu_to_le32(ft->tclass);
+	buf[2] = cpu_to_le32(ndatum);
+	items = put_entry(buf, sizeof(uint32_t), 3, fp);
+	if (items != 3)
+		return POLICYDB_ERROR;
+
+	datum = data;
+	do {
+		if (ebitmap_write(&datum->stypes, fp))
+			return POLICYDB_ERROR;
+
+		buf[0] = cpu_to_le32(datum->otype);
+		items = put_entry(buf, sizeof(uint32_t), 1, fp);
+		if (items != 1)
+			return POLICYDB_ERROR;
+
+		datum = datum->next;
+	} while (datum);
+
+	return 0;
+}
+
+static int avtab_filename_trans_write(policydb_t *pol, avtab_t *a,
+				      policy_file_t *fp)
+{
+	policydb_t *p = pol;
+	uint32_t buf[1];
+	int rc;
+	size_t items;
+	uint32_t i, nel = 0;
+	struct avtab_node *cur;
+	hashtab_t fnts_tab;
+	name_trans_write_args_t write_args = { .fp = fp };
+	name_trans_insert_args_t insert_args;
+
+	/* count number of filename transitions */
+	for (i = 0; i < a->nslot; i++) {
+		for (cur = a->htable[i]; cur; cur = cur->next) {
+			if (cur->key.specified & AVTAB_TRANSITION) {
+				nel += hashtab_nel(cur->datum.trans->name_trans.table);
+			}
+		}
+	}
+
+	if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
+		buf[0] = cpu_to_le32(nel);
+		items = put_entry(buf, sizeof(uint32_t), 1, fp);
+		if (items != 1)
+			return -1;
+
+		/* write filename transitions */
+		for (i = 0; i < a->nslot; i++) {
+			for (cur = a->htable[i]; cur; cur = cur->next) {
+				if (cur->key.specified & AVTAB_TRANSITION) {
+					write_args.key = &cur->key;
+					rc = hashtab_map(cur->datum.trans->name_trans.table,
+							 name_trans_write_helper,
+							 &write_args);
+					if (rc)
+						return -1;
+				}
+			}
+		}
+		return 0;
+	}
+
+	/* init filename transitions */
+	fnts_tab = hashtab_create(filenametr_hash, filenametr_cmp, nel);
+	if (!fnts_tab)
+		return -1;
+	insert_args.fnts_tab = fnts_tab;
+
+	for (i = 0; i < a->nslot; i++) {
+		for (cur = a->htable[i]; cur; cur = cur->next) {
+			if (cur->key.specified & AVTAB_TRANSITION) {
+				insert_args.av_key = &cur->key;
+				rc = hashtab_map(cur->datum.trans->name_trans.table,
+						 name_trans_insert_helper,
+						 &insert_args);
+			}
+		}
+	}
+
+	rc = -1;
+	/* write compressed filename transitions */
+	buf[0] = cpu_to_le32(fnts_tab->nel);
+	items = put_entry(buf, sizeof(uint32_t), 1, fp);
+	if (items != 1)
+		goto out;
+
+	rc = hashtab_map(fnts_tab, filenametr_comp_write_one, fp);
+
+out:
+	/* destroy temp filename transitions table */
+	hashtab_map(fnts_tab, filenametr_destroy, NULL);
+	hashtab_destroy(fnts_tab);
+
+	return rc ? -1 : 0;
+}
+
+/* end policydb filename transition compatibility */
+
 /*
  * Write a semantic MLS level structure to a policydb binary 
  * representation file.
@@ -580,118 +904,6 @@ static int role_allow_write(role_allow_t * r, struct policy_file *fp)
 	return POLICYDB_SUCCESS;
 }
 
-static int filename_write_one_compat(hashtab_key_t key, void *data, void *ptr)
-{
-	uint32_t bit, buf[4];
-	size_t items, len;
-	filename_trans_key_t *ft = (filename_trans_key_t *)key;
-	filename_trans_datum_t *datum = data;
-	ebitmap_node_t *node;
-	void *fp = ptr;
-
-	len = strlen(ft->name);
-	do {
-		ebitmap_for_each_positive_bit(&datum->stypes, node, bit) {
-			buf[0] = cpu_to_le32(len);
-			items = put_entry(buf, sizeof(uint32_t), 1, fp);
-			if (items != 1)
-				return POLICYDB_ERROR;
-
-			items = put_entry(ft->name, sizeof(char), len, fp);
-			if (items != len)
-				return POLICYDB_ERROR;
-
-			buf[0] = cpu_to_le32(bit + 1);
-			buf[1] = cpu_to_le32(ft->ttype);
-			buf[2] = cpu_to_le32(ft->tclass);
-			buf[3] = cpu_to_le32(datum->otype);
-			items = put_entry(buf, sizeof(uint32_t), 4, fp);
-			if (items != 4)
-				return POLICYDB_ERROR;
-		}
-
-		datum = datum->next;
-	} while (datum);
-
-	return 0;
-}
-
-static int filename_write_one(hashtab_key_t key, void *data, void *ptr)
-{
-	uint32_t buf[3];
-	size_t items, len, ndatum;
-	filename_trans_key_t *ft = (filename_trans_key_t *)key;
-	filename_trans_datum_t *datum;
-	void *fp = ptr;
-
-	len = strlen(ft->name);
-	buf[0] = cpu_to_le32(len);
-	items = put_entry(buf, sizeof(uint32_t), 1, fp);
-	if (items != 1)
-		return POLICYDB_ERROR;
-
-	items = put_entry(ft->name, sizeof(char), len, fp);
-	if (items != len)
-		return POLICYDB_ERROR;
-
-	ndatum = 0;
-	datum = data;
-	do {
-		ndatum++;
-		datum = datum->next;
-	} while (datum);
-
-	buf[0] = cpu_to_le32(ft->ttype);
-	buf[1] = cpu_to_le32(ft->tclass);
-	buf[2] = cpu_to_le32(ndatum);
-	items = put_entry(buf, sizeof(uint32_t), 3, fp);
-	if (items != 3)
-		return POLICYDB_ERROR;
-
-	datum = data;
-	do {
-		if (ebitmap_write(&datum->stypes, fp))
-			return POLICYDB_ERROR;
-
-		buf[0] = cpu_to_le32(datum->otype);
-		items = put_entry(buf, sizeof(uint32_t), 1, fp);
-		if (items != 1)
-			return POLICYDB_ERROR;
-
-		datum = datum->next;
-	} while (datum);
-
-	return 0;
-}
-
-static int filename_trans_write(struct policydb *p, void *fp)
-{
-	size_t items;
-	uint32_t buf[1];
-	int rc;
-
-	if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS)
-		return 0;
-
-	if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
-		buf[0] = cpu_to_le32(p->filename_trans_count);
-		items = put_entry(buf, sizeof(uint32_t), 1, fp);
-		if (items != 1)
-			return POLICYDB_ERROR;
-
-		rc = hashtab_map(p->filename_trans, filename_write_one_compat,
-				 fp);
-	} else {
-		buf[0] = cpu_to_le32(p->filename_trans->nel);
-		items = put_entry(buf, sizeof(uint32_t), 1, fp);
-		if (items != 1)
-			return POLICYDB_ERROR;
-
-		rc = hashtab_map(p->filename_trans, filename_write_one, fp);
-	}
-	return rc;
-}
-
 static int role_set_write(role_set_t * x, struct policy_file *fp)
 {
 	size_t items;
@@ -2202,6 +2414,21 @@ static int role_attr_uncount(hashtab_key_t key __attribute__ ((unused)),
 	return 0;
 }
 
+static int avtab_has_filename_transitions(avtab_t *a)
+{
+	uint32_t i;
+	struct avtab_node *cur;
+	for (i = 0; i < a->nslot; i++) {
+		for (cur = a->htable[i]; cur; cur = cur->next) {
+			if (cur->key.specified & AVTAB_TRANSITION) {
+				if (hashtab_nel(cur->datum.trans->name_trans.table))
+					return 1;
+			}
+		}
+	}
+	return 0;
+}
+
 /*
  * Write the configuration data in a policy database
  * structure to a policy database binary representation
@@ -2381,11 +2608,11 @@ int policydb_write(policydb_t * p, struct policy_file *fp)
 		if (role_allow_write(p->role_allow, fp))
 			return POLICYDB_ERROR;
 		if (p->policyvers >= POLICYDB_VERSION_FILENAME_TRANS) {
-			if (filename_trans_write(p, fp))
+			if (avtab_filename_trans_write(p, &p->te_avtab, fp))
 				return POLICYDB_ERROR;
-		} else {
-			if (p->filename_trans)
-				WARN(fp->handle, "Discarding filename type transition rules");
+		} else if (avtab_has_filename_transitions(&p->te_avtab)) {
+			WARN(fp->handle,
+			     "Discarding filename type transition rules");
 		}
 	} else {
 		if (avrule_block_write(p->global, num_syms, p, fp) == -1) {
