/*
 * Authors: Jan Zarsky <jzarsky@redhat.com>
 *
 * Copyright (C) 2019 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "utilities.h"
#include "test_fcontext.h"

char FCONTEXTS[] =
    "/etc/selinux(/.*) -s system_u:object_r:first_t:s0\n"
    "/etc/selinux/targeted -- system_u:object_r:second_t:s0\n"
    "/etc/selinux(/.*) -b system_u:object_r:third_t:s0\n";
unsigned int FCONTEXTS_LEN = sizeof(FCONTEXTS);

#define FCONTEXTS_COUNT 3

#define FCONTEXT1_EXPR "/etc/selinux(/.*)"
#define FCONTEXT1_TYPE SEMANAGE_FCONTEXT_SOCK
#define FCONTEXT1_CON "system_u:object_r:first_t:s0"

#define FCONTEXT2_EXPR "/etc/selinux/targeted"
#define FCONTEXT2_TYPE SEMANAGE_FCONTEXT_REG
#define FCONTEXT2_CON "system_u:object_r:second_t:s0"

#define FCONTEXT3_EXPR "/etc/selinux(/.*)"
#define FCONTEXT3_TYPE SEMANAGE_FCONTEXT_BLOCK
#define FCONTEXT3_CON "system_u:object_r:third_t:s0"

#define FCONTEXT_NONEXISTENT_EXPR "/asdf"
#define FCONTEXT_NONEXISTENT_TYPE SEMANAGE_FCONTEXT_ALL

/* fcontext_record.h */
static void test_fcontext_compare(void);
static void test_fcontext_compare2(void);
static void test_fcontext_key_create(void);
static void test_fcontext_key_extract(void);
static void test_fcontext_get_set_expr(void);
static void test_fcontext_get_set_type(void);
static void test_fcontext_get_type_str(void);
static void test_fcontext_get_set_con(void);
static void test_fcontext_create(void);
static void test_fcontext_clone(void);

/* fcontext_policy.h */
static void test_fcontext_query(void);
static void test_fcontext_exists(void);
static void test_fcontext_count(void);
static void test_fcontext_iterate(void);
static void test_fcontext_list(void);

/* fcontext_local.h */
static void test_fcontext_modify_del_local(void);
static void test_fcontext_query_local(void);
static void test_fcontext_exists_local(void);
static void test_fcontext_count_local(void);
static void test_fcontext_iterate_local(void);
static void test_fcontext_list_local(void);

extern semanage_handle_t *sh;

static int write_file_contexts(const char *data, unsigned int data_len)
{
	FILE *fptr = fopen("test-policy/store/active/file_contexts", "w+");

	if (!fptr) {
		perror("fopen");
		return -1;
	}

	if (fwrite(data, data_len, 1, fptr) != 1) {
		perror("fwrite");
		fclose(fptr);
		return -1;
	}

	fclose(fptr);

	return 0;
}

int fcontext_test_init(void)
{
	if (create_test_store() < 0) {
		fprintf(stderr, "Could not create test store\n");
		return 1;
	}

	if (write_test_policy_from_file("test_fcontext.policy") < 0) {
		fprintf(stderr, "Could not write test policy\n");
		return 1;
	}

	if (write_file_contexts(FCONTEXTS, FCONTEXTS_LEN) < 0) {
		fprintf(stderr, "Could not write file contexts\n");
		return 1;
	}

	return 0;
}

int fcontext_test_cleanup(void)
{
	if (destroy_test_store() < 0) {
		fprintf(stderr, "Could not destroy test store\n");
		return 1;
	}

	return 0;
}

int fcontext_add_tests(CU_pSuite suite)
{
	CU_add_test(suite, "test_fcontext_compare", test_fcontext_compare);
	CU_add_test(suite, "test_fcontext_compare2", test_fcontext_compare2);
	CU_add_test(suite, "test_fcontext_key_create",
		    test_fcontext_key_create);
	CU_add_test(suite, "test_fcontext_key_extract",
		    test_fcontext_key_extract);
	CU_add_test(suite, "test_fcontext_get_set_expr",
		    test_fcontext_get_set_expr);
	CU_add_test(suite, "test_fcontext_get_set_type",
		    test_fcontext_get_set_type);
	CU_add_test(suite, "test_fcontext_get_type_str",
		    test_fcontext_get_type_str);
	CU_add_test(suite, "test_fcontext_get_set_con",
		    test_fcontext_get_set_con);
	CU_add_test(suite, "test_fcontext_create", test_fcontext_create);
	CU_add_test(suite, "test_fcontext_clone", test_fcontext_clone);

	CU_add_test(suite, "test_fcontext_query", test_fcontext_query);
	CU_add_test(suite, "test_fcontext_exists", test_fcontext_exists);
	CU_add_test(suite, "test_fcontext_count", test_fcontext_count);
	CU_add_test(suite, "test_fcontext_iterate", test_fcontext_iterate);
	CU_add_test(suite, "test_fcontext_list", test_fcontext_list);
	CU_add_test(suite, "test_fcontext_modify_del_local",
		    test_fcontext_modify_del_local);
	CU_add_test(suite, "test_fcontext_query_local",
		    test_fcontext_query_local);
	CU_add_test(suite, "test_fcontext_exists_local",
		    test_fcontext_exists_local);
	CU_add_test(suite, "test_fcontext_count_local",
		    test_fcontext_count_local);
	CU_add_test(suite, "test_fcontext_iterate_local",
		    test_fcontext_iterate_local);
	CU_add_test(suite, "test_fcontext_list_local",
		    test_fcontext_list_local);

	return 0;
}

/* Helpers */

static semanage_fcontext_t *get_fcontext_nth(int idx)
{
	semanage_fcontext_t **records;
	semanage_fcontext_t *fcontext;
	unsigned int count;

	if (idx == I_NULL)
		return NULL;

	CU_ASSERT_FATAL(semanage_fcontext_list(sh, &records, &count) >= 0);
	CU_ASSERT_FATAL(count >= (unsigned int) idx + 1);

	fcontext = records[idx];

	for (unsigned int i = 0; i < count; i++)
		if (i != (unsigned int) idx)
			semanage_fcontext_free(records[i]);

	free(records);

	return fcontext;
}

static semanage_fcontext_key_t *get_fcontext_key_nth(int idx)
{
	semanage_fcontext_key_t *key;
	semanage_fcontext_t *fcontext;

	if (idx == I_NULL)
		return NULL;

	fcontext = get_fcontext_nth(idx);

	CU_ASSERT_FATAL(semanage_fcontext_key_extract(sh, fcontext, &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL_FATAL(key);

	semanage_fcontext_free(fcontext);

	return key;
}

static void add_local_fcontext(int fcontext_idx)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_key_t *key = NULL;

	CU_ASSERT_FATAL(fcontext_idx != I_NULL);

	fcontext = get_fcontext_nth(fcontext_idx);

	CU_ASSERT_FATAL(semanage_fcontext_key_extract(sh, fcontext, &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL_FATAL(key);

	CU_ASSERT_FATAL(semanage_fcontext_modify_local(sh, key, fcontext) >= 0);

	/* cleanup */
	semanage_fcontext_key_free(key);
	semanage_fcontext_free(fcontext);
}

static void delete_local_fcontext(int fcontext_idx)
{
	semanage_fcontext_key_t *key = NULL;

	CU_ASSERT_FATAL(fcontext_idx != I_NULL);

	key = get_fcontext_key_nth(fcontext_idx);

	CU_ASSERT_FATAL(semanage_fcontext_del_local(sh, key) >= 0);

	semanage_fcontext_key_free(key);
}

static semanage_fcontext_key_t *get_fcontext_key_from_str(const char *str, int type)
{
	semanage_fcontext_key_t *key;
	int res;

	if (str == NULL)
		return NULL;

	res = semanage_fcontext_key_create(sh, str, type, &key);

	CU_ASSERT_FATAL(res >= 0);
	CU_ASSERT_PTR_NOT_NULL_FATAL(key);

	return key;
}

/* Function semanage_fcontext_compare */
static void test_fcontext_compare(void)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_key_t *key1;
	semanage_fcontext_key_t *key2;
	semanage_fcontext_key_t *key3;

	/* setup */
	setup_handle(SH_CONNECT);

	fcontext = get_fcontext_nth(I_FIRST);

	key1 = get_fcontext_key_nth(I_FIRST);
	key2 = get_fcontext_key_nth(I_SECOND);
	key3 = get_fcontext_key_nth(I_THIRD);

	/* test */
	CU_ASSERT(semanage_fcontext_compare(fcontext, key1) == 0);
	CU_ASSERT(semanage_fcontext_compare(fcontext, key2) < 0);
	CU_ASSERT(semanage_fcontext_compare(fcontext, key3) > 0);

	/* cleanup */
	semanage_fcontext_free(fcontext);
	semanage_fcontext_key_free(key1);
	semanage_fcontext_key_free(key2);
	semanage_fcontext_key_free(key3);
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_compare2 */
static void test_fcontext_compare2(void)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_t *fcontext1;
	semanage_fcontext_t *fcontext2;
	semanage_fcontext_t *fcontext3;

	/* setup */
	setup_handle(SH_CONNECT);

	fcontext = get_fcontext_nth(I_FIRST);
	fcontext1 = get_fcontext_nth(I_FIRST);
	fcontext2 = get_fcontext_nth(I_SECOND);
	fcontext3 = get_fcontext_nth(I_THIRD);

	/* test */
	CU_ASSERT(semanage_fcontext_compare2(fcontext, fcontext1) == 0);
	CU_ASSERT(semanage_fcontext_compare2(fcontext, fcontext2) < 0);
	CU_ASSERT(semanage_fcontext_compare2(fcontext, fcontext3) > 0);

	/* cleanup */
	semanage_fcontext_free(fcontext);
	semanage_fcontext_free(fcontext1);
	semanage_fcontext_free(fcontext2);
	semanage_fcontext_free(fcontext3);
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_key_create */
static void test_fcontext_key_create(void)
{
	semanage_fcontext_key_t *key = NULL;

	/* setup */
	setup_handle(SH_CONNECT);

	/* test */
	CU_ASSERT(semanage_fcontext_key_create(sh, "", SEMANAGE_FCONTEXT_ALL,
					       &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL(key);

	semanage_fcontext_key_free(key);

	key = NULL;

	CU_ASSERT(semanage_fcontext_key_create(sh, "testfcontext",
					     SEMANAGE_FCONTEXT_ALL, &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL(key);

	semanage_fcontext_key_free(key);

	/* cleanup */
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_key_extract */
static void test_fcontext_key_extract(void)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_key_t *key;

	/* setup */
	setup_handle(SH_CONNECT);
	fcontext = get_fcontext_nth(I_FIRST);

	/* test */
	CU_ASSERT(semanage_fcontext_key_extract(sh, fcontext, &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL(key);

	/* cleanup */
	semanage_fcontext_key_free(key);
	semanage_fcontext_free(fcontext);
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_get_expr, semanage_fcontext_set_expr */
static void test_fcontext_get_set_expr(void)
{
	semanage_fcontext_t *fcontext;
	const char *expr = NULL;
	const char *expr_exp = "/asdf";

	/* setup */
	setup_handle(SH_CONNECT);
	fcontext = get_fcontext_nth(I_FIRST);

	/* test */
	CU_ASSERT(semanage_fcontext_set_expr(sh, fcontext, expr_exp) >= 0);
	expr = semanage_fcontext_get_expr(fcontext);
	CU_ASSERT_PTR_NOT_NULL(expr);
	assert(expr);
	CU_ASSERT_STRING_EQUAL(expr, expr_exp);

	/* cleanup */
	semanage_fcontext_free(fcontext);
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_get_type, semanage_fcontext_set_type */
static void test_fcontext_get_set_type(void)
{
	semanage_fcontext_t *fcontext;
	int type_exp = SEMANAGE_FCONTEXT_SOCK;
	int type;

	/* setup */
	setup_handle(SH_CONNECT);
	fcontext = get_fcontext_nth(I_FIRST);

	/* test */
	semanage_fcontext_set_type(fcontext, type_exp);
	type = semanage_fcontext_get_type(fcontext);
	CU_ASSERT(type == type_exp);

	/* cleanup */
	semanage_fcontext_free(fcontext);
	cleanup_handle(SH_CONNECT);
}

/* Function semanage_fcontext_get_type_str */
static void helper_fcontext_get_type_str(int type, const char *exp_str)
{
	CU_ASSERT_STRING_EQUAL(semanage_fcontext_get_type_str(type), exp_str);
}

static void test_fcontext_get_type_str(void)
{
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_ALL, "all files");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_REG, "regular file");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_DIR, "directory");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_CHAR,
				     "character device");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_BLOCK, "block device");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_SOCK, "socket");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_LINK, "symbolic link");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_PIPE, "named pipe");

	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_ALL - 1, "????");
	helper_fcontext_get_type_str(SEMANAGE_FCONTEXT_PIPE + 1, "????");
}

/* Function semanage_fcontext_get_con, semanage_fcontext_set_con */
static void helper_fcontext_get_set_con(level_t level, int fcontext_idx,
				 const char *con_str)
{
	semanage_fcontext_t *fcontext;
	semanage_context_t *con = NULL;
	semanage_context_t *new_con = NULL;

	/* setup */
	setup_handle(level);
	fcontext = get_fcontext_nth(fcontext_idx);

	if (con_str != NULL) {
		CU_ASSERT(semanage_context_from_string(sh, con_str, &con) >= 0);
		CU_ASSERT_PTR_NOT_NULL(con);
	} else {
		con = NULL;
	}

	/* test */
	CU_ASSERT(semanage_fcontext_set_con(sh, fcontext, con) >= 0);
	new_con = semanage_fcontext_get_con(fcontext);

	if (con_str != NULL) {
		CU_ASSERT_CONTEXT_EQUAL(con, new_con);
	} else {
		CU_ASSERT_PTR_NULL(new_con);
	}

	/* cleanup */
	semanage_context_free(con);
	semanage_fcontext_free(fcontext);
	cleanup_handle(level);
}

static void test_fcontext_get_set_con(void)
{
	helper_fcontext_get_set_con(SH_CONNECT, I_FIRST, NULL);
	helper_fcontext_get_set_con(SH_CONNECT, I_FIRST,
				    "user_u:role_r:type_t:s0");
	helper_fcontext_get_set_con(SH_CONNECT, I_SECOND,
				    "user_u:role_r:type_t:s0");
	helper_fcontext_get_set_con(SH_TRANS, I_FIRST, NULL);
	helper_fcontext_get_set_con(SH_TRANS, I_FIRST,
				    "user_u:role_r:type_t:s0");
	helper_fcontext_get_set_con(SH_TRANS, I_SECOND,
				    "user_u:role_r:type_t:s0");
}

/* Function semanage_fcontext_create */
static void helper_fcontext_create(level_t level)
{
	semanage_fcontext_t *fcontext;

	/* setup */
	setup_handle(level);

	/* test */
	CU_ASSERT(semanage_fcontext_create(sh, &fcontext) >= 0);
	CU_ASSERT_PTR_NULL(semanage_fcontext_get_expr(fcontext));
	CU_ASSERT(semanage_fcontext_get_type(fcontext)
		  == SEMANAGE_FCONTEXT_ALL);
	CU_ASSERT_PTR_NULL(semanage_fcontext_get_con(fcontext));

	/* cleanup */
	semanage_fcontext_free(fcontext);
	cleanup_handle(level);
}

static void test_fcontext_create(void)
{
	helper_fcontext_create(SH_NULL);
	helper_fcontext_create(SH_HANDLE);
	helper_fcontext_create(SH_CONNECT);
	helper_fcontext_create(SH_TRANS);
}

/* Function semanage_fcontext_clone */
static void helper_fcontext_clone(level_t level, int fcontext_idx)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_t *fcontext_clone;
	const char *expr;
	const char *expr_clone;
	int type;
	int type_clone;
	semanage_context_t *con;
	semanage_context_t *con_clone;

	/* setup */
	setup_handle(level);
	fcontext = get_fcontext_nth(fcontext_idx);

	/* test */
	CU_ASSERT(semanage_fcontext_clone(sh, fcontext, &fcontext_clone) >= 0);

	expr = semanage_fcontext_get_expr(fcontext);
	expr_clone = semanage_fcontext_get_expr(fcontext_clone);
	CU_ASSERT_STRING_EQUAL(expr, expr_clone);

	type = semanage_fcontext_get_type(fcontext);
	type_clone = semanage_fcontext_get_type(fcontext_clone);
	CU_ASSERT_EQUAL(type, type_clone);

	con = semanage_fcontext_get_con(fcontext);
	con_clone = semanage_fcontext_get_con(fcontext_clone);
	CU_ASSERT_CONTEXT_EQUAL(con, con_clone);

	/* cleanup */
	semanage_fcontext_free(fcontext);
	semanage_fcontext_free(fcontext_clone);
	cleanup_handle(level);
}

static void test_fcontext_clone(void)
{
	helper_fcontext_clone(SH_CONNECT, I_FIRST);
	helper_fcontext_clone(SH_CONNECT, I_SECOND);
	helper_fcontext_clone(SH_TRANS, I_FIRST);
	helper_fcontext_clone(SH_TRANS, I_SECOND);
}

/* Function semanage_fcontext_query */
static void helper_fcontext_query(level_t level, const char *fcontext_expr,
			   int fcontext_type, int exp_res)
{
	semanage_fcontext_key_t *key;
	semanage_fcontext_t *resp = (void *) 42;
	int res;

	/* setup */
	setup_handle(level);
	key = get_fcontext_key_from_str(fcontext_expr, fcontext_type);

	/* test */
	res = semanage_fcontext_query(sh, key, &resp);

	if (exp_res >= 0) {
		CU_ASSERT(res >= 0);
		const char *expr = semanage_fcontext_get_expr(resp);
		CU_ASSERT_STRING_EQUAL(expr, fcontext_expr);
		semanage_fcontext_free(resp);
	} else {
		CU_ASSERT(res < 0);
		CU_ASSERT(resp == (void *) 42);
	}

	/* cleanup */
	semanage_fcontext_key_free(key);
	cleanup_handle(level);
}

static void test_fcontext_query(void)
{
	helper_fcontext_query(SH_CONNECT, FCONTEXT_NONEXISTENT_EXPR,
			      FCONTEXT_NONEXISTENT_TYPE, -1);
	helper_fcontext_query(SH_CONNECT, FCONTEXT2_EXPR, FCONTEXT1_TYPE, -1);
	helper_fcontext_query(SH_CONNECT, FCONTEXT1_EXPR, FCONTEXT1_TYPE, 1);
	helper_fcontext_query(SH_CONNECT, FCONTEXT2_EXPR, FCONTEXT2_TYPE, 1);
	helper_fcontext_query(SH_TRANS, FCONTEXT_NONEXISTENT_EXPR,
			      FCONTEXT_NONEXISTENT_TYPE, -1);
	helper_fcontext_query(SH_TRANS, FCONTEXT2_EXPR, FCONTEXT1_TYPE, -1);
	helper_fcontext_query(SH_TRANS, FCONTEXT1_EXPR, FCONTEXT1_TYPE, 1);
	helper_fcontext_query(SH_TRANS, FCONTEXT2_EXPR, FCONTEXT2_TYPE, 1);
}

/* Function semanage_fcontext_exists */
static void helper_fcontext_exists(level_t level, const char *fcontext_expr,
			    int fcontext_type, int exp_resp)
{
	semanage_fcontext_key_t *key;
	int resp;

	/* setup */
	setup_handle(level);
	key = get_fcontext_key_from_str(fcontext_expr, fcontext_type);

	/* test */
	CU_ASSERT(semanage_fcontext_exists(sh, key, &resp) >= 0);
	CU_ASSERT(resp == exp_resp);

	/* cleanup */
	semanage_fcontext_key_free(key);
	cleanup_handle(level);
}

static void test_fcontext_exists(void)
{
	helper_fcontext_exists(SH_CONNECT, FCONTEXT_NONEXISTENT_EXPR,
			       FCONTEXT_NONEXISTENT_TYPE, 0);
	helper_fcontext_exists(SH_CONNECT, FCONTEXT2_EXPR, FCONTEXT1_TYPE, 0);
	helper_fcontext_exists(SH_CONNECT, FCONTEXT1_EXPR, FCONTEXT1_TYPE, 1);
	helper_fcontext_exists(SH_CONNECT, FCONTEXT2_EXPR, FCONTEXT2_TYPE, 1);
	helper_fcontext_exists(SH_TRANS, FCONTEXT_NONEXISTENT_EXPR,
			       FCONTEXT_NONEXISTENT_TYPE, 0);
	helper_fcontext_exists(SH_TRANS, FCONTEXT2_EXPR, FCONTEXT1_TYPE, 0);
	helper_fcontext_exists(SH_TRANS, FCONTEXT1_EXPR, FCONTEXT1_TYPE, 1);
	helper_fcontext_exists(SH_TRANS, FCONTEXT2_EXPR, FCONTEXT2_TYPE, 1);
}

/* Function semanage_fcontext_count */
static void test_fcontext_count(void)
{
	unsigned int resp;

	/* handle */
	setup_handle(SH_HANDLE);
	CU_ASSERT(semanage_fcontext_count(sh, &resp) < 0);
	CU_ASSERT(semanage_fcontext_count(sh, NULL) < 0);
	cleanup_handle(SH_HANDLE);

	/* connect */
	resp = 0;
	setup_handle(SH_CONNECT);
	CU_ASSERT(semanage_fcontext_count(sh, &resp) >= 0);
	CU_ASSERT(resp == FCONTEXTS_COUNT);
	cleanup_handle(SH_CONNECT);

	/* trans */
	resp = 0;
	setup_handle(SH_TRANS);
	CU_ASSERT(semanage_fcontext_count(sh, &resp) >= 0);
	CU_ASSERT(resp == FCONTEXTS_COUNT);
	cleanup_handle(SH_TRANS);
}

/* Function semanage_fcontext_iterate */
unsigned int counter_fcontext_iterate = 0;

static int handler_fcontext_iterate(const semanage_fcontext_t *record, void *varg)
{
	CU_ASSERT_PTR_NOT_NULL(record);
	counter_fcontext_iterate++;
	return 0;
}

static void helper_fcontext_iterate_invalid(void)
{
	/* setup */
	setup_handle(SH_HANDLE);

	/* test */
	CU_ASSERT(semanage_fcontext_iterate(sh, &handler_fcontext_iterate,
				            NULL) < 0);
	CU_ASSERT(semanage_fcontext_iterate(sh, NULL, NULL) < 0);

	/* cleanup */
	cleanup_handle(SH_HANDLE);
}

static void helper_fcontext_iterate(level_t level)
{
	/* setup */
	setup_handle(level);
	counter_fcontext_iterate = 0;

	/* test */
	CU_ASSERT(semanage_fcontext_iterate(sh, &handler_fcontext_iterate,
					    NULL) >= 0);
	CU_ASSERT(counter_fcontext_iterate == FCONTEXTS_COUNT);

	/* cleanup */
	cleanup_handle(level);
}

static void test_fcontext_iterate(void)
{
	helper_fcontext_iterate_invalid();
	helper_fcontext_iterate(SH_CONNECT);
	helper_fcontext_iterate(SH_TRANS);
}

/* Function semanage_fcontext_list */
static void helper_fcontext_list_invalid(void)
{
	semanage_fcontext_t **records;
	unsigned int count;

	/* setup */
	setup_handle(SH_HANDLE);

	/* test */
	CU_ASSERT(semanage_fcontext_list(sh, &records, &count) < 0);
	CU_ASSERT(semanage_fcontext_list(sh, NULL, &count) < 0);
	CU_ASSERT(semanage_fcontext_list(sh, &records, NULL) < 0);

	/* cleanup */
	cleanup_handle(SH_HANDLE);
}

static void helper_fcontext_list(level_t level)
{
	semanage_fcontext_t **records;
	unsigned int count;

	/* setup */
	setup_handle(level);

	/* test */
	CU_ASSERT(semanage_fcontext_list(sh, &records, &count) >= 0);
	CU_ASSERT(count == FCONTEXTS_COUNT);

	for (unsigned int i = 0; i < count; i++)
		CU_ASSERT_PTR_NOT_NULL(records[i]);

	for (unsigned int i = 0; i < count; i++)
		semanage_fcontext_free(records[i]);

	free(records);

	/* cleanup */
	cleanup_handle(level);
}

static void test_fcontext_list(void)
{
	helper_fcontext_list_invalid();
	helper_fcontext_list(SH_CONNECT);
	helper_fcontext_list(SH_TRANS);
}

/* Function semanage_fcontext_modify_local, semanage_fcontext_del_local */
static void helper_fcontext_modify_del_local(level_t level, int fcontext_idx,
				      const char *con_str, int exp_res)
{
	semanage_fcontext_t *fcontext;
	semanage_fcontext_t *fcontext_local = NULL;
	semanage_fcontext_key_t *key = NULL;
	semanage_context_t *con = NULL;
	int res;

	/* setup */
	setup_handle(level);
	fcontext = get_fcontext_nth(fcontext_idx);
	CU_ASSERT(semanage_fcontext_key_extract(sh, fcontext, &key) >= 0);
	CU_ASSERT_PTR_NOT_NULL(key);

	if (con_str != NULL) {
		CU_ASSERT(semanage_context_from_string(sh, con_str, &con) >= 0);
		CU_ASSERT_PTR_NOT_NULL(con);
	} else {
		con = NULL;
	}

	CU_ASSERT(semanage_fcontext_set_con(sh, fcontext, con) >= 0);

	/* test */
	res = semanage_fcontext_modify_local(sh, key, fcontext);

	if (exp_res >= 0) {
		CU_ASSERT(res >= 0);

		if (level == SH_TRANS) {
			helper_commit();
			helper_begin_transaction();
		}

		CU_ASSERT(semanage_fcontext_query_local(sh, key,
					                &fcontext_local) >= 0);
		CU_ASSERT(semanage_fcontext_compare2(fcontext_local,
						     fcontext) == 0);
		semanage_fcontext_free(fcontext_local);

		CU_ASSERT(semanage_fcontext_del_local(sh, key) >= 0);
		CU_ASSERT(semanage_fcontext_query_local(sh, key,
					                &fcontext_local) < 0);
	} else {
		CU_ASSERT(res < 0);
	}

	/* cleanup */
	semanage_context_free(con);
	semanage_fcontext_key_free(key);
	semanage_fcontext_free(fcontext);
	cleanup_handle(level);
}

static void test_fcontext_modify_del_local(void)
{
	helper_fcontext_modify_del_local(SH_CONNECT, I_FIRST,
					 "system_u:object_r:tmp_t:s0", -1);
	helper_fcontext_modify_del_local(SH_CONNECT, I_SECOND,
					 "system_u:object_r:tmp_t:s0", -1);
	helper_fcontext_modify_del_local(SH_TRANS, I_FIRST,
					 "system_u:object_r:tmp_t:s0", 1);
	helper_fcontext_modify_del_local(SH_TRANS, I_SECOND,
					 "system_u:object_r:tmp_t:s0", 1);
}

/* Function semanage_fcontext_query_local */
static void test_fcontext_query_local(void)
{
	semanage_fcontext_key_t *key = NULL;
	semanage_fcontext_t *resp = NULL;

	/* connect */
	setup_handle(SH_CONNECT);

	key = get_fcontext_key_nth(I_FIRST);
	CU_ASSERT(semanage_fcontext_query_local(sh, key, &resp) < 0);
	CU_ASSERT_PTR_NULL(resp);

	cleanup_handle(SH_CONNECT);

	/* transaction */
	setup_handle(SH_TRANS);

	semanage_fcontext_key_free(key);
	key = get_fcontext_key_nth(I_FIRST);
	CU_ASSERT(semanage_fcontext_query_local(sh, key, &resp) < 0);
	CU_ASSERT_PTR_NULL(resp);

	add_local_fcontext(I_FIRST);
	CU_ASSERT(semanage_fcontext_query_local(sh, key, &resp) >= 0);
	CU_ASSERT_PTR_NOT_NULL(resp);
	semanage_fcontext_free(resp);
	resp = NULL;

	semanage_fcontext_key_free(key);
	key = get_fcontext_key_nth(I_SECOND);
	add_local_fcontext(I_SECOND);
	CU_ASSERT(semanage_fcontext_query_local(sh, key, &resp) >= 0);
	CU_ASSERT_PTR_NOT_NULL(resp);
	semanage_fcontext_free(resp);
	resp = NULL;

	/* cleanup */
	semanage_fcontext_key_free(key);
	delete_local_fcontext(I_FIRST);
	delete_local_fcontext(I_SECOND);
	cleanup_handle(SH_TRANS);
}

/* Function semanage_fcontext_exists_local */
static void test_fcontext_exists_local(void)
{
	int resp = -1;
	semanage_fcontext_key_t *key;

	/* setup */
	setup_handle(SH_TRANS);
	key = get_fcontext_key_nth(I_FIRST);

	/* test */
	CU_ASSERT(semanage_fcontext_exists_local(sh, key, &resp) >= 0);
	CU_ASSERT(resp == 0);

	add_local_fcontext(I_FIRST);
	resp = -1;

	CU_ASSERT(semanage_fcontext_exists_local(sh, key, &resp) >= 0);
	CU_ASSERT(resp == 1);

	delete_local_fcontext(I_FIRST);
	resp = -1;

	CU_ASSERT(semanage_fcontext_exists_local(sh, key, &resp) >= 0);
	CU_ASSERT(resp == 0);

	resp = -1;

	CU_ASSERT(semanage_fcontext_exists_local(sh, NULL, &resp) >= 0);
	CU_ASSERT(resp == 0);

	/* cleanup */
	semanage_fcontext_key_free(key);
	cleanup_handle(SH_TRANS);
}

/* Function semanage_fcontext_count_local */
static void test_fcontext_count_local(void)
{
	unsigned int resp;

	/* handle */
	setup_handle(SH_HANDLE);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) < 0);
	cleanup_handle(SH_HANDLE);

	/* connect */
	setup_handle(SH_CONNECT);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) >= 0);
	CU_ASSERT(resp == 0);
	cleanup_handle(SH_CONNECT);

	/* transaction */
	setup_handle(SH_TRANS);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) >= 0);
	CU_ASSERT(resp == 0);

	add_local_fcontext(I_FIRST);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) >= 0);
	CU_ASSERT(resp == 1);

	add_local_fcontext(I_SECOND);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) >= 0);
	CU_ASSERT(resp == 2);

	delete_local_fcontext(I_SECOND);
	CU_ASSERT(semanage_fcontext_count_local(sh, &resp) >= 0);
	CU_ASSERT(resp == 1);

	/* cleanup */
	delete_local_fcontext(I_FIRST);
	cleanup_handle(SH_TRANS);
}

/* Function semanage_fcontext_iterate_local */
unsigned int counter_fcontext_iterate_local = 0;

static int handler_fcontext_iterate_local(const semanage_fcontext_t *record,
				   void *varg)
{
	CU_ASSERT_PTR_NOT_NULL(record);
	counter_fcontext_iterate_local++;
	return 0;
}

static void test_fcontext_iterate_local(void)
{
	/* handle */
	setup_handle(SH_HANDLE);

	CU_ASSERT(semanage_fcontext_iterate_local(sh,
				    &handler_fcontext_iterate_local, NULL) < 0);
	CU_ASSERT(semanage_fcontext_iterate_local(sh, NULL, NULL) < 0);

	cleanup_handle(SH_HANDLE);

	/* connect */
	setup_handle(SH_CONNECT);

	counter_fcontext_iterate_local = 0;
	CU_ASSERT(semanage_fcontext_iterate_local(sh,
				   &handler_fcontext_iterate_local, NULL) >= 0);
	CU_ASSERT(counter_fcontext_iterate_local == 0);
	CU_ASSERT(semanage_fcontext_iterate_local(sh, NULL, NULL) >= 0);

	cleanup_handle(SH_CONNECT);

	/* transaction */
	setup_handle(SH_TRANS);

	counter_fcontext_iterate_local = 0;
	CU_ASSERT(semanage_fcontext_iterate_local(sh,
				   &handler_fcontext_iterate_local, NULL) >= 0);
	CU_ASSERT(counter_fcontext_iterate_local == 0);

	add_local_fcontext(I_FIRST);
	counter_fcontext_iterate_local = 0;
	CU_ASSERT(semanage_fcontext_iterate_local(sh,
				   &handler_fcontext_iterate_local, NULL) >= 0);
	CU_ASSERT(counter_fcontext_iterate_local == 1);

	add_local_fcontext(I_SECOND);
	counter_fcontext_iterate_local = 0;
	CU_ASSERT(semanage_fcontext_iterate_local(sh,
				   &handler_fcontext_iterate_local, NULL) >= 0);
	CU_ASSERT(counter_fcontext_iterate_local == 2);

	/* cleanup */
	delete_local_fcontext(I_FIRST);
	delete_local_fcontext(I_SECOND);
	cleanup_handle(SH_TRANS);
}

/* Function semanage_fcontext_list_local */
static void test_fcontext_list_local(void)
{
	semanage_fcontext_t **records;
	unsigned int count;

	/* handle */
	setup_handle(SH_HANDLE);

	CU_ASSERT(semanage_fcontext_list_local(sh, &records, &count) < 0);
	CU_ASSERT(semanage_fcontext_list_local(sh, NULL, &count) < 0);
	CU_ASSERT(semanage_fcontext_list_local(sh, &records, NULL) < 0);

	cleanup_handle(SH_HANDLE);

	/* connect */
	setup_handle(SH_CONNECT);

	CU_ASSERT(semanage_fcontext_list_local(sh, &records, &count) >= 0);
	CU_ASSERT(count == 0);

	cleanup_handle(SH_CONNECT);

	/* transaction */
	setup_handle(SH_TRANS);

	CU_ASSERT(semanage_fcontext_list_local(sh, &records, &count) >= 0);
	CU_ASSERT(count == 0);

	add_local_fcontext(I_FIRST);
	CU_ASSERT(semanage_fcontext_list_local(sh, &records, &count) >= 0);
	CU_ASSERT(count == 1);
	CU_ASSERT_PTR_NOT_NULL(records[0]);
	semanage_fcontext_free(records[0]);
	free(records);

	add_local_fcontext(I_SECOND);
	CU_ASSERT(semanage_fcontext_list_local(sh, &records, &count) >= 0);
	CU_ASSERT(count == 2);
	CU_ASSERT_PTR_NOT_NULL(records[0]);
	CU_ASSERT_PTR_NOT_NULL(records[1]);
	semanage_fcontext_free(records[0]);
	semanage_fcontext_free(records[1]);
	free(records);

	/* cleanup */
	delete_local_fcontext(I_FIRST);
	delete_local_fcontext(I_SECOND);
	cleanup_handle(SH_TRANS);
}
