/* Copyright (C) 2005 Red Hat, Inc. */

/* Object: dbase_policydb_t (Policy)
 * Implements: dbase_t (Database)
 */

struct dbase_policydb;
typedef struct dbase_policydb dbase_t;
#define DBASE_DEFINED

#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <errno.h>

#include <sepol/policydb.h>

#include "database_policydb.h"
#include "semanage_store.h"
#include "handle.h"
#include "debug.h"

/* POLICYDB dbase */
struct dbase_policydb {

        /* Backing path for read-only[0] and transaction[1] */
        const char *path[2];

	/* Base record table */
	record_table_t *rtable;

	/* Policy extensions */
	record_policydb_table_t *rptable;

	sepol_policydb_t *policydb;

	int cache_serial;
	int modified;
	int attached;
};

static void dbase_policydb_drop_cache(dbase_policydb_t * dbase)
{

	if (dbase->cache_serial >= 0) {
		sepol_policydb_free(dbase->policydb);
		dbase->cache_serial = -1;
		dbase->modified = 0;
	}
}

static int dbase_policydb_set_serial(semanage_handle_t * handle,
				     dbase_policydb_t * dbase)
{

	int cache_serial = handle->funcs->get_serial(handle);
	if (cache_serial < 0) {
		ERR(handle, "could not update cache serial");
		return STATUS_ERR;
	}

	dbase->cache_serial = cache_serial;
	return STATUS_SUCCESS;
}

static int dbase_policydb_needs_resync(semanage_handle_t * handle,
				       dbase_policydb_t * dbase)
{

	int cache_serial;

	if (dbase->cache_serial < 0)
		return 1;

	cache_serial = handle->funcs->get_serial(handle);
	if (cache_serial < 0)
		return 1;

	if (cache_serial != dbase->cache_serial) {
		dbase_policydb_drop_cache(dbase);
		dbase->cache_serial = -1;
		return 1;
	}
	return 0;
}

static int dbase_policydb_cache(semanage_handle_t * handle,
				dbase_policydb_t * dbase)
{

	FILE *fp = NULL;
	sepol_policydb_t *policydb = NULL;
	sepol_policy_file_t *pf = NULL;
	const char *fname = NULL;

	/* Check if cache is needed */
	if (dbase->attached)
		return STATUS_SUCCESS;

	if (!dbase_policydb_needs_resync(handle, dbase))
		return STATUS_SUCCESS;

	fname = dbase->path[handle->is_in_transaction];

	if (sepol_policydb_create(&policydb) < 0) {
		ERR(handle, "could not create policydb object");
		goto err;
	}

	/* Try opening file 
	 * ENOENT is not fatal - we just create an empty policydb */
	fp = fopen(fname, "rb");
	if (fp == NULL && errno != ENOENT) {
		ERR(handle, "could not open %s for reading: %s",
		    fname, strerror(errno));
		goto err;
	}

	/* If the file was opened successfully, read a policydb */
	if (fp != NULL) {
		__fsetlocking(fp, FSETLOCKING_BYCALLER);
		if (sepol_policy_file_create(&pf) < 0) {
			ERR(handle, "could not create policy file object");
			goto err;
		}

		sepol_policy_file_set_fp(pf, fp);
		sepol_policy_file_set_handle(pf, handle->sepolh);

		if (sepol_policydb_read(policydb, pf) < 0)
			goto err;

		sepol_policy_file_free(pf);
		fclose(fp);
		fp = NULL;
	}

	/* Update cache serial */
	if (dbase_policydb_set_serial(handle, dbase) < 0)
		goto err;

	/* Update the database policydb */
	dbase->policydb = policydb;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not cache policy database");
	if (fp)
		fclose(fp);
	sepol_policydb_free(policydb);
	sepol_policy_file_free(pf);
	return STATUS_ERR;
}

static int dbase_policydb_flush(semanage_handle_t * handle
				__attribute__ ((unused)),
				dbase_policydb_t * dbase)
{

	if (!dbase->modified)
		return STATUS_SUCCESS;

	dbase->modified = 0;

	/* Stub */
	handle = NULL;
	return STATUS_ERR;
}

/* Check if modified */
static int dbase_policydb_is_modified(dbase_policydb_t * dbase)
{

	return dbase->modified;
}

int dbase_policydb_init(semanage_handle_t * handle,
			const char *path_ro,
			const char *path_rw,
			record_table_t * rtable,
			record_policydb_table_t * rptable,
			dbase_policydb_t ** dbase)
{

	dbase_policydb_t *tmp_dbase =
	    (dbase_policydb_t *) malloc(sizeof(dbase_policydb_t));

	if (!tmp_dbase)
		goto omem;

	tmp_dbase->path[0] = path_ro;
	tmp_dbase->path[1] = path_rw;
	tmp_dbase->rtable = rtable;
	tmp_dbase->rptable = rptable;
	tmp_dbase->policydb = NULL;
	tmp_dbase->cache_serial = -1;
	tmp_dbase->modified = 0;
	tmp_dbase->attached = 0;
	*dbase = tmp_dbase;

	return STATUS_SUCCESS;

      omem:
	ERR(handle, "out of memory, could not initialize policy database");
	free(tmp_dbase);

	return STATUS_ERR;
}

/* Release dbase resources */
void dbase_policydb_release(dbase_policydb_t * dbase)
{

	dbase_policydb_drop_cache(dbase);
	free(dbase);
}

/* Attach to a shared policydb.
 * This implies drop_cache(),
 * and prevents flush() and drop_cache()
 * until detached. */
void dbase_policydb_attach(dbase_policydb_t * dbase,
			   sepol_policydb_t * policydb)
{

	dbase->attached = 1;
	dbase_policydb_drop_cache(dbase);
	dbase->policydb = policydb;
}

/* Detach from a shared policdb.
 * This implies drop_cache. */
void dbase_policydb_detach(dbase_policydb_t * dbase)
{

	dbase->attached = 0;
	dbase->modified = 0;
}

static int dbase_policydb_add(semanage_handle_t * handle,
			      dbase_policydb_t * dbase,
			      const record_key_t * key, const record_t * data)
{

	if (dbase->rptable->add(handle->sepolh, dbase->policydb, key, data) < 0)
		goto err;

	dbase->modified = 1;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not add record to the database");
	return STATUS_ERR;
}

static int dbase_policydb_set(semanage_handle_t * handle,
			      dbase_policydb_t * dbase,
			      const record_key_t * key, const record_t * data)
{

	if (dbase->rptable->set(handle->sepolh, dbase->policydb, key, data) < 0)
		goto err;

	dbase->modified = 1;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not set record value");
	return STATUS_ERR;
}

static int dbase_policydb_modify(semanage_handle_t * handle,
				 dbase_policydb_t * dbase,
				 const record_key_t * key,
				 const record_t * data)
{

	if (dbase->rptable->modify(handle->sepolh,
				   dbase->policydb, key, data) < 0)
		goto err;

	dbase->modified = 1;
	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not modify record value");
	return STATUS_ERR;
}

static int dbase_policydb_del(semanage_handle_t * handle
				__attribute__ ((unused)),
			      dbase_policydb_t * dbase
				__attribute__ ((unused)),
			      const record_key_t * key
				__attribute__ ((unused)))
{

	/* Stub */
	key = NULL;
	handle = NULL;
	dbase = NULL;
	return STATUS_ERR;
}

static int dbase_policydb_clear(semanage_handle_t * handle
				__attribute__ ((unused)),
				dbase_policydb_t * dbase
				__attribute__ ((unused)))
{

	/* Stub */
	handle = NULL;
	dbase = NULL;
	return STATUS_ERR;
}

static int dbase_policydb_query(semanage_handle_t * handle,
				dbase_policydb_t * dbase,
				const record_key_t * key, record_t ** response)
{

	if (dbase->rptable->query(handle->sepolh,
				  dbase->policydb, key, response) < 0)
		goto err;

	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not query record value");
	return STATUS_ERR;
}

static int dbase_policydb_exists(semanage_handle_t * handle,
				 dbase_policydb_t * dbase,
				 const record_key_t * key, int *response)
{

	if (dbase->rptable->exists(handle->sepolh,
				   dbase->policydb, key, response) < 0)
		goto err;

	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not check if record exists");
	return STATUS_ERR;
}

static int dbase_policydb_count(semanage_handle_t * handle,
				dbase_policydb_t * dbase,
				unsigned int *response)
{

	if (dbase->rptable->count(handle->sepolh,
				  dbase->policydb, response) < 0)
		goto err;

	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not count the database records");
	return STATUS_ERR;
}

static int dbase_policydb_iterate(semanage_handle_t * handle,
				  dbase_policydb_t * dbase,
				  int (*fn) (const record_t * record,
					     void *fn_arg), void *arg)
{

	if (dbase->rptable->iterate(handle->sepolh,
				    dbase->policydb, fn, arg) < 0)
		goto err;

	return STATUS_SUCCESS;

      err:
	ERR(handle, "could not iterate over records");
	return STATUS_ERR;
}

struct list_handler_arg {
	semanage_handle_t *handle;
	record_table_t *rtable;
	record_t **records;
	int pos;
};

static int list_handler(const record_t * record, void *varg)
{

	struct list_handler_arg *arg = (struct list_handler_arg *)varg;

	if (arg->rtable->clone(arg->handle, record, &arg->records[arg->pos]) <
	    0)
		return -1;
	arg->pos++;
	return 0;
}

static int dbase_policydb_list(semanage_handle_t * handle,
			       dbase_t * dbase,
			       record_t *** records, unsigned int *count)
{

	record_t **tmp_records = NULL;
	unsigned int tmp_count;
	struct list_handler_arg list_arg;
	list_arg.pos = 0;
	list_arg.rtable = dbase->rtable;
	list_arg.handle = handle;

	if (dbase->rptable->count(handle->sepolh,
				  dbase->policydb, &tmp_count) < 0)
		goto err;

	if (tmp_count > 0) {
		tmp_records = (record_t **)
		    calloc(tmp_count, sizeof(record_t *));

		if (tmp_records == NULL)
			goto omem;

		list_arg.records = tmp_records;

		if (dbase->rptable->iterate(handle->sepolh,
					    dbase->policydb, list_handler,
					    &list_arg) < 0) {
			ERR(handle, "list handler could not extract record");
			goto err;
		}
	}

	*records = tmp_records;
	*count = tmp_count;
	return STATUS_SUCCESS;

      omem:
	ERR(handle, "out of memory");

      err:
	if (tmp_records) {
		for (; list_arg.pos >= 0; list_arg.pos--)
			dbase->rtable->free(tmp_records[list_arg.pos]);
		free(tmp_records);
	}
	ERR(handle, "could not list records");
	return STATUS_ERR;
}

static record_table_t *dbase_policydb_get_rtable(dbase_policydb_t * dbase)
{

	return dbase->rtable;
}

/* POLICYDB dbase - method table implementation */
dbase_table_t SEMANAGE_POLICYDB_DTABLE = {

	/* Cache/Transactions */
	.cache = dbase_policydb_cache,
	.drop_cache = dbase_policydb_drop_cache,
	.flush = dbase_policydb_flush,
	.is_modified = dbase_policydb_is_modified,

	/* Database Functionality */
	.iterate = dbase_policydb_iterate,
	.exists = dbase_policydb_exists,
	.list = dbase_policydb_list,
	.add = dbase_policydb_add,
	.set = dbase_policydb_set,
	.del = dbase_policydb_del,
	.clear = dbase_policydb_clear,
	.modify = dbase_policydb_modify,
	.query = dbase_policydb_query,
	.count = dbase_policydb_count,

	/* Polymorphism */
	.get_rtable = dbase_policydb_get_rtable
};