/*
 * Copyright (c) 2003-2015
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 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.
 *
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * This is a VFS layer for Berkeley DB.
 * There are several databases (each corresponding to a different OBJ_NAME),
 * each of which contains objects of a particular type (group definitions or
 * ACLs or DTDs or ...).
 *
 * There needs to be a utility command to get/put objects for initialization
 * and editing purposes.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_db.c 2829 2015-08-10 22:32:35Z brachman $";
#endif

#include "local.h"
#include "vfs.h"

#ifdef ENABLE_BDB
#include "kwv.h"

#include <db.h>

static const char *log_module_name = "vfs_db";

#ifndef BDB_DEFAULT_FILE_MODE
#define BDB_DEFAULT_FILE_MODE		0644
#endif

static int db_open(Vfs_handle *, char *naming_context);
static int db_close(Vfs_handle *handle);
static int db_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int db_get(Vfs_handle *handle, char *key, void **buffer,
				  size_t *length);
static int db_getsize(Vfs_handle *handle, char *key, size_t *length);
static int db_put(Vfs_handle *handle, char *key, void *buffer,
				  size_t length);
static int db_delete(Vfs_handle *handle, char *key);
static int db_exists(Vfs_handle *handle, char *key);
static int db_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int db_list(Vfs_handle *handle, int (*is_valid)(char *),
				   int (*compar)(const void *, const void *),
				   int (*add)(char *, char *, void ***), void ***names);

static Vfs_switch db_conf = {
  "db",
  db_open,
  db_close,
  db_control,
  db_get,
  db_getsize,
  db_put,
  db_delete,
  db_exists,
  db_rename,
  db_list
};

typedef struct {
  DB *db;
  char *dbfile;
} Handle;

static int
db_open(Vfs_handle *handle, char *naming_context)
{
  int st;
  u_int32_t flags;
  mode_t mode;
  DB *db;
  Handle *h;

  if ((st = db_create(&db, NULL, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  if (handle->delete_flag && unlink(naming_context) == -1
	  && errno != ENOENT) {
	handle->error_num = errno;		/* XXX Not a real DB error message. */
	handle->error_msg = strerror(errno);
	return(-1);
  }

  flags = 0;
  if (access(naming_context, F_OK) == -1 && errno == ENOENT
	  && handle->create_flag)
	flags |= DB_CREATE;

  if (access(naming_context, W_OK) == -1 && errno == EACCES)
	flags |= DB_RDONLY;

  if (handle->mode == 0)
	mode = BDB_DEFAULT_FILE_MODE;
  else
	mode = handle->mode;

  st = db->open(db, NULL, naming_context, NULL, DB_HASH, flags, mode);
  if (st != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  h = ALLOC(Handle);
  h->dbfile = strdup(naming_context);
  h->db = db;

  handle->h = (void *) h;
  return(0);
}

static int
db_close(Vfs_handle *handle)
{
  int st;
  Handle *h;

  h = (Handle *) handle->h;
  if ((st = (h->db->close)(h->db, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
  }

  free(h->dbfile);
  free(h);

  return((st == 0) ? 0 : -1);
}

static int
db_control(Vfs_handle *handle, Vfs_control_op op, va_list ap)
{
  char **pptr;
  Handle *h;

  h = (Handle *) handle->h;
  if (h == NULL || h->dbfile == NULL)
	return(-1);

  switch (op) {
  case VFS_GET_CONTAINER:
	pptr = va_arg(ap, char **);
	*pptr = ds_xprintf("file:%s", h->dbfile);
	break;

  case VFS_SET_FIELD_SEP:
	/* Nothing to do... */
	break;

  case VFS_GET_FIELD_SEP:
	return(-1);
	/*NOTREACHED*/
	break;

  default:
	log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));
	return(-1);
	/*NOTREACHED*/
	break;
  }

  return(0);
}

static int
db_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st;
  DBT k, v;
  Handle *h;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&k, 0, sizeof(k));
  memset(&v, 0, sizeof(v));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.data = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.data = ds_xprintf("%s", key);
  k.size = (u_int32_t) strlen((char *) k.data) + handle->null_flag;
  v.flags = DB_DBT_USERMEM;
  st = (h->db->get)(h->db, NULL, &k, &v, 0);
  if (st != ENOMEM && st != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  free(k.data);
  *length = v.size;
  return(0);
}

static int
db_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int st;
  char *b;
  DBT k, v;
  Handle *h;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&k, 0, sizeof(k));
  memset(&v, 0, sizeof(v));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.data = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.data = ds_xprintf("%s", key);
  k.size = strlen((char *) k.data) + handle->null_flag;
  k.flags = 0;
  v.data = NULL;
  v.flags = 0;
  st = (h->db->get)(h->db, NULL, &k, &v, 0);
  if (st != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  free(k.data);

  b = (char *) malloc((size_t) v.size + 1);
  memcpy(b, v.data, v.size);
  b[v.size] = '\0';

  if (length != NULL)
	*length = v.size;
  *buffer = (void *) b;

  return(0);
}

/*
 * By default, multiple data items are not permitted, and each database
 * store operation will overwrite any previous data item for that key. 
 */
static int
db_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int st;
  DBT k, v;
  Handle *h;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&k, 0, sizeof(k));
  memset(&v, 0, sizeof(v));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.data = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.data = ds_xprintf("%s", key);
  k.size = strlen((char *) k.data) + handle->null_flag;
  k.flags = 0;
  v.data = buffer;
  v.size = length;
  v.flags = 0;
  if ((st = (h->db->put)(h->db, NULL, &k, &v, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  free(k.data);
  return(0);
}

static int
db_delete(Vfs_handle *handle, char *key)
{
  int st;
  DBT k;
  Handle *h;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&k, 0, sizeof(k));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.data = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.data = ds_xprintf("%s", key);
  k.size = strlen((char *) k.data) + handle->null_flag;
  k.flags = 0;
  if ((st = (h->db->del)(h->db, NULL, &k, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  return(0);
}

static int
db_exists(Vfs_handle *handle, char *key)
{
  int st;
  DBT k, v;
  Handle *h;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&k, 0, sizeof(k));
  memset(&v, 0, sizeof(v));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.data = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.data = ds_xprintf("%s", key);
  k.size = strlen((char *) k.data) + handle->null_flag;

  v.data = NULL;
  v.size = 0;
  v.dlen = v.doff = 0;
  v.flags = DB_DBT_USERMEM;

  /*
   * Berkeley DB Reference Guide, Partial record storage and retrieval
   */
  v.flags |= DB_DBT_PARTIAL;
  v.dlen = 0;
  v.doff = 0;
  st = (h->db->get)(h->db, NULL, &k, &v, 0);

  free(k.data);

  if (st == ENOMEM || st == 0)
	return(1);
  if (st == DB_NOTFOUND)
	return(0);

  handle->error_num = st;
  handle->error_msg = strdup(db_strerror(st));
  return(-1);
}

static int
db_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  int st;
  DBT okey, nkey, v;
  Handle *h;

  if (oldkey == NULL || newkey == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  memset(&okey, 0, sizeof(okey));
  memset(&nkey, 0, sizeof(nkey));
  memset(&v, 0, sizeof(v));

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	okey.data = ds_xprintf("%s/%s", handle->sd->item_type, oldkey);
  else
	okey.data = ds_xprintf("%s", oldkey);
  okey.size = strlen((char *) okey.data) + handle->null_flag;
  okey.flags = 0;

  /* XXX This sequence should be atomic */
  if ((st = (h->db->get)(h->db, NULL, &okey, &v, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  if (handle->sd->item_type != NULL)
	nkey.data = ds_xprintf("%s/%s", handle->sd->item_type, newkey);
  else
	nkey.data = ds_xprintf("%s", newkey);
  nkey.size = strlen((char *) nkey.data) + handle->null_flag;
  nkey.flags = 0;
  if ((st = (h->db->put)(h->db, NULL, &nkey, &v, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  if ((st = (h->db->del)(h->db, NULL, &okey, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  free(okey.data);
  free(nkey.data);

  return(0);
}

static int
db_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{
  int flags, n, st;
  char *key;
  size_t nc_len;
  DBT k, v;
  Handle *h;
  DBC *cursor;

  h = (Handle *) handle->h;
  if ((st = (h->db->cursor)(h->db, NULL, &cursor, 0)) != 0) {
	handle->error_num = st;
	handle->error_msg = strdup(db_strerror(st));
	return(-1);
  }

  n = 0;
  flags = DB_FIRST;
  if (handle->sd->item_type != NULL)
	nc_len = strlen(handle->sd->item_type);
  else
	nc_len = 0;

  while (1) {
	memset(&k, 0, sizeof(k));
	memset(&v, 0, sizeof(v));

	k.flags = 0;
	v.data = NULL;
	v.size = 0;
	v.dlen = v.doff = 0;
	v.flags = DB_DBT_PARTIAL;
	if ((st = cursor->c_get(cursor, &k, &v, flags)) != 0) {
	  if (st == DB_NOTFOUND)
		break;
	  handle->error_num = st;
	  handle->error_msg = strdup(db_strerror(st));
	  cursor->c_close(cursor);
	  return(-1);
	}
	flags = DB_NEXT;

	if (handle->null_flag)
	  key = (char *) k.data;
	else
	  key = strndup(k.data, k.size);

	if (handle->sd->item_type != NULL) {
	  if (!strneq(key, handle->sd->item_type, nc_len))
		continue;
	}

	if (is_valid == NULL || is_valid(key + nc_len)) {
	  if (add(handle->sd->item_type, key, names) == 1)
		n++;
	}
  }
  cursor->c_close(cursor);

  if (compar != NULL) {
	if (handle->list_sort == NULL)
	  qsort(names, n, sizeof(void **), compar);
	else
	  handle->list_sort(names, n, sizeof(void **), compar);
  }

  return(n);
}

Vfs_switch *
vfs_db_init(char *store_name)
{

  return(&db_conf);
}

#else

Vfs_switch *
vfs_db_init(char *store_name)
{

  return(NULL);
}

#endif
