/*
   Unix SMB/CIFS implementation.

   ntdb utility functions

   Copyright (C) Rusty Russell 2012

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#include "util_ntdb.h"
#include "lib/param/param.h"
#include "replace.h"
#include "system/filesys.h"

/*
 * This handles NTDB_CLEAR_IF_FIRST.
 *
 * It's a bad idea for new code, but S3 uses it quite a bit.
 */
static enum NTDB_ERROR clear_if_first(int fd, void *unused)
{
	/* We hold a lock offset 4 always, so we can tell if anyone else is. */
	struct flock fl;

	fl.l_type = F_WRLCK;
	fl.l_whence = SEEK_SET;
	fl.l_start = 4; /* ACTIVE_LOCK */
	fl.l_len = 1;

	if (fcntl(fd, F_SETLK, &fl) == 0) {
		/* We must be first ones to open it w/ NTDB_CLEAR_IF_FIRST! */
		if (ftruncate(fd, 0) != 0) {
			return NTDB_ERR_IO;
		}
	}
	fl.l_type = F_RDLCK;
	if (fcntl(fd, F_SETLKW, &fl) != 0) {
		return NTDB_ERR_IO;
	}
	return NTDB_SUCCESS;
}

/* We only need these for the CLEAR_IF_FIRST lock. */
static int reacquire_cif_lock(struct ntdb_context *ntdb, bool *fail)
{
	struct flock fl;
	union ntdb_attribute cif;

	cif.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
	cif.openhook.base.next = NULL;

	if (ntdb_get_attribute(ntdb, &cif) != NTDB_SUCCESS
	    || cif.openhook.fn != clear_if_first) {
		return 0;
	}

	/* We hold a lock offset 4 always, so we can tell if anyone else is. */
	fl.l_type = F_RDLCK;
	fl.l_whence = SEEK_SET;
	fl.l_start = 4; /* ACTIVE_LOCK */
	fl.l_len = 1;
	if (fcntl(ntdb_fd(ntdb), F_SETLKW, &fl) != 0) {
		*fail = true;
		return -1;
	}
	return 0;
}

/* You only need this on databases with NTDB_CLEAR_IF_FIRST */
int ntdb_reopen(struct ntdb_context *ntdb)
{
	bool unused;
	return reacquire_cif_lock(ntdb, &unused);
}

/* You only need to do this if you have NTDB_CLEAR_IF_FIRST databases, and
 * the parent will go away before this child. */
int ntdb_reopen_all(void)
{
	bool fail = false;

	ntdb_foreach(reacquire_cif_lock, &fail);
	if (fail)
		return -1;
	return 0;
}

static void *ntdb_talloc(const void *owner, size_t len, void *priv_data)
{
	return talloc_size(owner, len);
}

static void *ntdb_expand(void *old, size_t newlen, void *priv_data)
{
	return talloc_realloc_size(NULL, old, newlen);
}

static void ntdb_free(void *old, void *priv_data)
{
	talloc_free(old);
}

static int ntdb_destroy(struct ntdb_context *ntdb)
{
	ntdb_close(ntdb);
	return 0;
}

static void ntdb_log(struct ntdb_context *ntdb,
		     enum ntdb_log_level level,
		     enum NTDB_ERROR ecode,
		     const char *message,
		     void *unused)
{
	int dl;
	const char *name = ntdb_name(ntdb);

	switch (level) {
	case NTDB_LOG_USE_ERROR:
	case NTDB_LOG_ERROR:
		dl = 0;
		break;
	case NTDB_LOG_WARNING:
		dl = 2;
		break;
	default:
		dl = 0;
	}

	DEBUG(dl, ("ntdb(%s):%s: %s\n", name ? name : "unnamed",
		   ntdb_errorstr(ecode), message));
}

struct ntdb_context *ntdb_new(TALLOC_CTX *ctx,
			      const char *name, int ntdb_flags,
			      int open_flags, mode_t mode,
			      union ntdb_attribute *attr,
			      struct loadparm_context *lp_ctx)
{
	union ntdb_attribute log_attr, alloc_attr, open_attr;
	struct ntdb_context *ntdb;

	if (lp_ctx && !lpcfg_use_mmap(lp_ctx)) {
		ntdb_flags |= NTDB_NOMMAP;
	}

	/* Great hack for speeding testing! */
	if (getenv("TDB_NO_FSYNC")) {
		ntdb_flags |= NTDB_NOSYNC;
	}

	log_attr.base.next = attr;
	log_attr.base.attr = NTDB_ATTRIBUTE_LOG;
	log_attr.log.fn = ntdb_log;

	alloc_attr.base.next = &log_attr;
	alloc_attr.base.attr = NTDB_ATTRIBUTE_ALLOCATOR;
	alloc_attr.alloc.alloc = ntdb_talloc;
	alloc_attr.alloc.expand = ntdb_expand;
	alloc_attr.alloc.free = ntdb_free;

	if (ntdb_flags & NTDB_CLEAR_IF_FIRST) {
		log_attr.base.next = &open_attr;
		open_attr.openhook.base.attr = NTDB_ATTRIBUTE_OPENHOOK;
		open_attr.openhook.base.next = attr;
		open_attr.openhook.fn = clear_if_first;
		ntdb_flags &= ~NTDB_CLEAR_IF_FIRST;
	}

	ntdb = ntdb_open(name, ntdb_flags, open_flags, mode, &alloc_attr);
	if (!ntdb) {
		return NULL;
	}

	/* We can re-use the tdb's path name for the talloc name */
	name = ntdb_name(ntdb);
	if (name) {
		talloc_set_name_const(ntdb, name);
	} else {
		talloc_set_name_const(ntdb, "unnamed ntdb");
	}
	talloc_set_destructor(ntdb, ntdb_destroy);

	return talloc_steal(ctx, ntdb);
}

enum NTDB_ERROR ntdb_lock_bystring(struct ntdb_context *ntdb,
				   const char *keyval)
{
	NTDB_DATA key = string_term_ntdb_data(keyval);

	return ntdb_chainlock(ntdb, key);
}

void ntdb_unlock_bystring(struct ntdb_context *ntdb, const char *keyval)
{
	NTDB_DATA key = string_term_ntdb_data(keyval);

	ntdb_chainunlock(ntdb, key);
}

enum NTDB_ERROR ntdb_delete_bystring(struct ntdb_context *ntdb,
				     const char *keystr)
{
	NTDB_DATA key = string_term_ntdb_data(keystr);

	return ntdb_delete(ntdb, key);
}

enum NTDB_ERROR ntdb_store_bystring(struct ntdb_context *ntdb,
				    const char *keystr,
				    NTDB_DATA data, int nflags)
{
	NTDB_DATA key = string_term_ntdb_data(keystr);

	return ntdb_store(ntdb, key, data, nflags);
}

enum NTDB_ERROR ntdb_fetch_bystring(struct ntdb_context *ntdb,
				    const char *keystr,
				    NTDB_DATA *data)
{
	NTDB_DATA key = string_term_ntdb_data(keystr);

	return ntdb_fetch(ntdb, key, data);
}

enum NTDB_ERROR ntdb_fetch_int32(struct ntdb_context *ntdb,
				 const char *keystr, int32_t *val)
{
	NTDB_DATA data;
	enum NTDB_ERROR err;

	err = ntdb_fetch(ntdb, string_term_ntdb_data(keystr), &data);
	if (err == NTDB_SUCCESS) {
		if (data.dsize != sizeof(*val)) {
			err = NTDB_ERR_CORRUPT;
		} else {
			*val = IVAL(data.dptr,0);
		}
		talloc_free(data.dptr);
	}
	return NTDB_SUCCESS;
}

enum NTDB_ERROR ntdb_store_int32(struct ntdb_context *ntdb,
				 const char *keystr, int32_t val)
{
	NTDB_DATA data, key;
	int32_t v_store;

	SIVAL(&v_store, 0, val);
	data = ntdb_mkdata(&v_store, sizeof(v_store));
	key = string_term_ntdb_data(keystr);

	return ntdb_store(ntdb, key, data, NTDB_REPLACE);
}

enum NTDB_ERROR ntdb_add_int32_atomic(struct ntdb_context *ntdb,
				      const char *keystr,
				      int32_t *oldval, int32_t addval)
{
	int32_t val;
	enum NTDB_ERROR err;

	err = ntdb_lock_bystring(ntdb, keystr);
	if (err) {
		return err;
	}

	err = ntdb_fetch_int32(ntdb, keystr, &val);
	if (err) {
		if (err == NTDB_ERR_NOEXIST) {
			/* Start with 'old' value */
			val = *oldval;
		} else {
			goto err_out;
		}
	} else {
		/* It worked, set return value (oldval) to tdb data */
		*oldval = val;
	}

	/* Increase value and store for next time. */
	val += addval;
	err = ntdb_store_int32(ntdb, keystr, val);

  err_out:
	ntdb_unlock_bystring(ntdb, keystr);
	return err;
}

NTSTATUS map_nt_error_from_ntdb(enum NTDB_ERROR err)
{
	NTSTATUS result = NT_STATUS_INTERNAL_ERROR;

	switch (err) {
	case NTDB_SUCCESS:
		result = NT_STATUS_OK;
		break;
	case NTDB_ERR_CORRUPT:
		result = NT_STATUS_INTERNAL_DB_CORRUPTION;
		break;
	case NTDB_ERR_IO:
		result = NT_STATUS_UNEXPECTED_IO_ERROR;
		break;
	case NTDB_ERR_OOM:
		result = NT_STATUS_NO_MEMORY;
		break;
	case NTDB_ERR_EXISTS:
		result = NT_STATUS_OBJECT_NAME_COLLISION;
		break;

	case NTDB_ERR_LOCK:
		/*
		 * NTDB_ERR_LOCK is very broad, we could for example
		 * distinguish between fcntl locks and invalid lock
		 * sequences. So NT_STATUS_FILE_LOCK_CONFLICT is a
		 * compromise.
		 */
		result = NT_STATUS_FILE_LOCK_CONFLICT;
		break;
	case NTDB_ERR_NOEXIST:
		result = NT_STATUS_NOT_FOUND;
		break;
	case NTDB_ERR_EINVAL:
		result = NT_STATUS_INVALID_PARAMETER;
		break;
	case NTDB_ERR_RDONLY:
		result = NT_STATUS_ACCESS_DENIED;
		break;
	};
	return result;
}