/*	$OpenBSD: namespace.c,v 1.13 2014/09/13 16:06:37 doug Exp $ */

/*
 * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/queue.h>

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>

#include "ldapd.h"

/* Maximum number of requests to queue per namespace during compaction.
 * After this many requests, we return LDAP_BUSY.
 */
#define MAX_REQUEST_QUEUE	 10000

static struct btval	*namespace_find(struct namespace *ns, char *dn);
static void		 namespace_queue_replay(int fd, short event, void *arg);
static int		 namespace_set_fd(struct namespace *ns,
			    struct btree **bt, int fd, unsigned int flags);

int
namespace_begin_txn(struct namespace *ns, struct btree_txn **data_txn,
    struct btree_txn **indx_txn, int rdonly)
{
	if (ns->data_db == NULL || ns->indx_db == NULL) {
		errno = EBUSY;	/* namespace is being reopened */
		return -1;
	}

	if ((*data_txn = btree_txn_begin(ns->data_db, rdonly)) == NULL ||
	    (*indx_txn = btree_txn_begin(ns->indx_db, rdonly)) == NULL) {
		if (errno == ESTALE) {
			if (*data_txn == NULL)
				namespace_reopen_data(ns);
			else
				namespace_reopen_indx(ns);
			errno = EBUSY;
		}
		log_warn("failed to open transaction");
		btree_txn_abort(*data_txn);
		*data_txn = NULL;
		return -1;
	}

	return 0;
}

int
namespace_begin(struct namespace *ns)
{
	return namespace_begin_txn(ns, &ns->data_txn, &ns->indx_txn, 0);
}

int
namespace_commit(struct namespace *ns)
{
	if (ns->indx_txn != NULL &&
	    btree_txn_commit(ns->indx_txn) != BT_SUCCESS) {
		log_warn("%s(indx): commit failed", ns->suffix);
		btree_txn_abort(ns->data_txn);
		ns->indx_txn = ns->data_txn = NULL;
		return -1;
	}
	ns->indx_txn = NULL;

	if (ns->data_txn != NULL &&
	    btree_txn_commit(ns->data_txn) != BT_SUCCESS) {
		log_warn("%s(data): commit failed", ns->suffix);
		ns->data_txn = NULL;
		return -1;
	}
	ns->data_txn = NULL;

	return 0;
}

void
namespace_abort(struct namespace *ns)
{
	btree_txn_abort(ns->data_txn);
	ns->data_txn = NULL;

	btree_txn_abort(ns->indx_txn);
	ns->indx_txn = NULL;
}

int
namespace_open(struct namespace *ns)
{
	unsigned int	 db_flags = 0;

	assert(ns);
	assert(ns->suffix);

	if (ns->sync == 0)
		db_flags |= BT_NOSYNC;

	if (asprintf(&ns->data_path, "%s/%s_data.db", DATADIR, ns->suffix) < 0)
		return -1;
	log_info("opening namespace %s", ns->suffix);
	ns->data_db = btree_open(ns->data_path, db_flags | BT_REVERSEKEY, 0644);
	if (ns->data_db == NULL)
		return -1;

	btree_set_cache_size(ns->data_db, ns->cache_size);

	if (asprintf(&ns->indx_path, "%s/%s_indx.db", DATADIR, ns->suffix) < 0)
		return -1;
	ns->indx_db = btree_open(ns->indx_path, db_flags, 0644);
	if (ns->indx_db == NULL)
		return -1;

	btree_set_cache_size(ns->indx_db, ns->index_cache_size);

	/* prepare request queue scheduler */
	evtimer_set(&ns->ev_queue, namespace_queue_replay, ns);

	return 0;
}

static int
namespace_reopen(const char *path)
{
	struct open_req		 req;

	log_debug("asking parent to open %s", path);

	bzero(&req, sizeof(req));
	if (strlcpy(req.path, path, sizeof(req.path)) >= sizeof(req.path)) {
		log_warnx("%s: path truncated", __func__);
		return -1;
	}

	return imsgev_compose(iev_ldapd, IMSG_LDAPD_OPEN, 0, 0, -1, &req,
	    sizeof(req));
}

int
namespace_reopen_data(struct namespace *ns)
{
	if (ns->data_db != NULL) {
		btree_close(ns->data_db);
		ns->data_db = NULL;
		return namespace_reopen(ns->data_path);
	}
	return 1;
}

int
namespace_reopen_indx(struct namespace *ns)
{
	if (ns->indx_db != NULL) {
		btree_close(ns->indx_db);
		ns->indx_db = NULL;
		return namespace_reopen(ns->indx_path);
	}
	return 1;
}

static int
namespace_set_fd(struct namespace *ns, struct btree **bt, int fd,
    unsigned int flags)
{
	log_info("reopening namespace %s (entries)", ns->suffix);
	btree_close(*bt);
	if (ns->sync == 0)
		flags |= BT_NOSYNC;
	*bt = btree_open_fd(fd, flags);
	if (*bt == NULL)
		return -1;
	return 0;
}

int
namespace_set_data_fd(struct namespace *ns, int fd)
{
	return namespace_set_fd(ns, &ns->data_db, fd, BT_REVERSEKEY);
}

int
namespace_set_indx_fd(struct namespace *ns, int fd)
{
	return namespace_set_fd(ns, &ns->indx_db, fd, 0);
}

void
namespace_close(struct namespace *ns)
{
	struct conn		*conn;
	struct search		*search, *next;
	struct request		*req;

	/* Cancel any queued requests for this namespace.
	 */
	if (ns->queued_requests > 0) {
		log_warnx("cancelling %u queued requests on namespace %s",
		    ns->queued_requests, ns->suffix);
		while ((req = TAILQ_FIRST(&ns->request_queue)) != NULL) {
			TAILQ_REMOVE(&ns->request_queue, req, next);
			ldap_respond(req, LDAP_UNAVAILABLE);
		}
	}

	/* Cancel any searches on this namespace.
	 */
	TAILQ_FOREACH(conn, &conn_list, next) {
		for (search = TAILQ_FIRST(&conn->searches); search != NULL;
		    search = next) {
			next = TAILQ_NEXT(search, next);
			if (search->ns == ns)
				search_close(search);
		}
	}

	free(ns->suffix);
	btree_close(ns->data_db);
	btree_close(ns->indx_db);
	if (evtimer_pending(&ns->ev_queue, NULL))
		evtimer_del(&ns->ev_queue);
	free(ns->data_path);
	free(ns->indx_path);
	free(ns);
}

void
namespace_remove(struct namespace *ns)
{
	TAILQ_REMOVE(&conf->namespaces, ns, next);
	namespace_close(ns);
}

static struct btval *
namespace_find(struct namespace *ns, char *dn)
{
	struct btval		 key;
	static struct btval	 val;

	if (ns->data_db == NULL) {
		errno = EBUSY;	/* namespace is being reopened */
		return NULL;
	}

	bzero(&key, sizeof(key));
	bzero(&val, sizeof(val));

	key.data = dn;
	key.size = strlen(dn);

	if (btree_txn_get(ns->data_db, ns->data_txn, &key, &val) != 0) {
		if (errno == ENOENT)
			log_debug("%s: dn not found", dn);
		else
			log_warn("%s", dn);

		if (errno == ESTALE)
			namespace_reopen_data(ns);

		return NULL;
	}

	return &val;
}

struct ber_element *
namespace_get(struct namespace *ns, char *dn)
{
	struct ber_element	*elm;
	struct btval		*val;

	if ((val = namespace_find(ns, dn)) == NULL)
		return NULL;

	elm = namespace_db2ber(ns, val);
	btval_reset(val);
	return elm;
}

int
namespace_exists(struct namespace *ns, char *dn)
{
	struct btval		*val;

	if ((val = namespace_find(ns, dn)) == NULL)
		return 0;
	btval_reset(val);
	return 1;
}

int
namespace_ber2db(struct namespace *ns, struct ber_element *root,
    struct btval *val)
{
	return ber2db(root, val, ns->compression_level);
}

struct ber_element *
namespace_db2ber(struct namespace *ns, struct btval *val)
{
	return db2ber(val, ns->compression_level);
}

static int
namespace_put(struct namespace *ns, char *dn, struct ber_element *root,
    int update)
{
	int			 rc;
	struct btval		 key, val;

	assert(ns != NULL);
	assert(ns->data_txn != NULL);
	assert(ns->indx_txn != NULL);

	bzero(&key, sizeof(key));
	key.data = dn;
	key.size = strlen(dn);

	if (namespace_ber2db(ns, root, &val) != 0)
		return BT_FAIL;

	rc = btree_txn_put(NULL, ns->data_txn, &key, &val,
	    update ? 0 : BT_NOOVERWRITE);
	if (rc != BT_SUCCESS) {
		if (errno == EEXIST)
			log_debug("%s: already exists", dn);
		else
			log_warn("%s", dn);
		goto done;
	}

	/* FIXME: if updating, try harder to just update changed indices.
	 */
	if (update && (rc = unindex_entry(ns, &key, root)) != BT_SUCCESS)
		goto done;

	rc = index_entry(ns, &key, root);

done:
	btval_reset(&val);
	return rc;
}

int
namespace_add(struct namespace *ns, char *dn, struct ber_element *root)
{
	return namespace_put(ns, dn, root, 0);
}

int
namespace_update(struct namespace *ns, char *dn, struct ber_element *root)
{
	return namespace_put(ns, dn, root, 1);
}

int
namespace_del(struct namespace *ns, char *dn)
{
	int			 rc;
	struct ber_element	*root;
	struct btval		 key, data;

	assert(ns != NULL);
	assert(ns->indx_txn != NULL);
	assert(ns->data_txn != NULL);

	bzero(&key, sizeof(key));
	bzero(&data, sizeof(data));

	key.data = dn;
	key.size = strlen(key.data);

	rc = btree_txn_del(NULL, ns->data_txn, &key, &data);
	if (rc == BT_SUCCESS && (root = namespace_db2ber(ns, &data)) != NULL)
		rc = unindex_entry(ns, &key, root);

	btval_reset(&data);
	return rc;
}

int
namespace_has_referrals(struct namespace *ns)
{
	return !SLIST_EMPTY(&ns->referrals);
}

struct namespace *
namespace_lookup_base(const char *basedn, int include_referrals)
{
	size_t			 blen, slen;
	struct namespace	*ns, *matched_ns = NULL;

	assert(basedn);
	blen = strlen(basedn);

	TAILQ_FOREACH(ns, &conf->namespaces, next) {
		slen = strlen(ns->suffix);
		if ((include_referrals || !namespace_has_referrals(ns)) &&
		    blen >= slen &&
		    bcmp(basedn + blen - slen, ns->suffix, slen) == 0) {
			/* Match the longest namespace suffix. */
			if (matched_ns == NULL ||
			    strlen(ns->suffix) > strlen(matched_ns->suffix))
				matched_ns = ns;
		}
	}

	return matched_ns;
}

struct namespace *
namespace_for_base(const char *basedn)
{
	return namespace_lookup_base(basedn, 0);
}

struct referrals *
namespace_referrals(const char *basedn)
{
	struct namespace	*ns;

	if ((ns = namespace_lookup_base(basedn, 1)) != NULL &&
	    namespace_has_referrals(ns))
		return &ns->referrals;

	if (!SLIST_EMPTY(&conf->referrals))
		return &conf->referrals;

	return NULL;
}

int
namespace_has_index(struct namespace *ns, const char *attr,
    enum index_type type)
{
	struct attr_index	*ai;

	assert(ns);
	assert(attr);
	TAILQ_FOREACH(ai, &ns->indices, next) {
		if (strcasecmp(attr, ai->attr) == 0 && ai->type == type)
			return 1;
	}

	return 0;
}

/* Queues modification requests while the namespace is being reopened.
 */
int
namespace_queue_request(struct namespace *ns, struct request *req)
{
	if (ns->queued_requests > MAX_REQUEST_QUEUE) {
		log_warn("%u requests alreay queued, sorry");
		return -1;
	}

	TAILQ_INSERT_TAIL(&ns->request_queue, req, next);
	ns->queued_requests++;

	if (!evtimer_pending(&ns->ev_queue, NULL))
		namespace_queue_schedule(ns, 250000);

	return 0;
}

static void
namespace_queue_replay(int fd, short event, void *data)
{
	struct namespace	*ns = data;
	struct request		*req;

	if (ns->data_db == NULL || ns->indx_db == NULL) {
		log_debug("%s: database is being reopened", ns->suffix);
		return;		/* Database is being reopened. */
	}

	if ((req = TAILQ_FIRST(&ns->request_queue)) == NULL)
		return;
	TAILQ_REMOVE(&ns->request_queue, req, next);

	log_debug("replaying queued request");
	req->replayed = 1;
	request_dispatch(req);
	ns->queued_requests--;

	if (!evtimer_pending(&ns->ev_queue, NULL))
		namespace_queue_schedule(ns, 0);
}

void
namespace_queue_schedule(struct namespace *ns, unsigned int usec)
{
	struct timeval	 tv;

	tv.tv_sec = 0;
	tv.tv_usec = usec;
	evtimer_add(&ns->ev_queue, &tv);
}

/* Cancel all queued requests from the given connection. Drops matching
 * requests from all namespaces without sending a response.
 */
void
namespace_cancel_conn(struct conn *conn)
{
	struct namespace	*ns;
	struct request		*req, *next;

	TAILQ_FOREACH(ns, &conf->namespaces, next) {
		for (req = TAILQ_FIRST(&ns->request_queue); req != NULL;
		    req = next) {
			next = TAILQ_NEXT(req, next);

			if (req->conn == conn) {
				TAILQ_REMOVE(&ns->request_queue, req, next);
				request_free(req);
			}
		}
	}
}

int
namespace_conn_queue_count(struct conn *conn)
{
	struct namespace	*ns;
	struct request		*req;
	int			 count = 0;

	TAILQ_FOREACH(ns, &conf->namespaces, next) {
		TAILQ_FOREACH(req, &ns->request_queue, next) {
			if (req->conn == conn)
				count++;
		}
	}

	return count;
}