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

/*
 * This is a VFS layer for the ndbm/gdbm database interface.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_ndbm.c 2507 2011-09-23 16:53:01Z brachman $";
#endif

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

#if defined(ENABLE_NDBM) || defined(ENABLE_GDBM)

/*
 * If we're using gdbm, the gdbm compatibility file needs to be used here.
 */
#include <ndbm.h>

/* We need to provide function prototypes. */
extern DBM *dbm_open(const char *file, int flags, int mode);
extern void dbm_close(DBM *db);
extern datum dbm_fetch(DBM *db, datum key);
extern datum dbm_firstkey(DBM *db);
extern datum dbm_nextkey(DBM *db);
extern int dbm_delete(DBM *db, datum key);
extern int dbm_store(DBM *db, datum key, datum data, int flags);
#ifndef dbm_error
extern int dbm_error(DBM *db);
#endif
extern int dbm_dirfno(DBM *db);

static const char *log_module_name = "vfs_ndbm";

#ifndef NDBM_DEFAULT_FILE_MODE
#define NDBM_DEFAULT_FILE_MODE		0644
#endif

static int vfs_ndbm_open(Vfs_handle *, char *naming_context);
static int vfs_ndbm_close(Vfs_handle *handle);
static int vfs_ndbm_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int vfs_ndbm_get(Vfs_handle *handle, char *key, void **buffer,
					size_t *length);
static int vfs_ndbm_getsize(Vfs_handle *handle, char *key, size_t *length);
static int vfs_ndbm_put(Vfs_handle *handle, char *key, void *buffer,
					size_t length);
static int vfs_ndbm_delete(Vfs_handle *handle, char *key);
static int vfs_ndbm_exists(Vfs_handle *handle, char *key);
static int vfs_ndbm_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int vfs_ndbm_list(Vfs_handle *handle, int (*is_valid)(char *),
					 int (*compar)(const void *, const void *),
					 int (*add)(char *, char *, void ***), void ***names);

static Vfs_switch vfs_ndbm_conf = {
  "ndbm",
  vfs_ndbm_open,
  vfs_ndbm_close,
  vfs_ndbm_control,
  vfs_ndbm_get,
  vfs_ndbm_getsize,
  vfs_ndbm_put,
  vfs_ndbm_delete,
  vfs_ndbm_exists,
  vfs_ndbm_rename,
  vfs_ndbm_list
};

typedef struct {
  DBM *dbm;
  char *dbmfile;
} Handle;

static int
vfs_ndbm_open(Vfs_handle *handle, char *naming_context)
{
  u_int32_t flags;
  mode_t mode;
  DBM *dbm;
  Handle *h;

  if (handle->delete_flag && unlink(naming_context) == -1
	  && errno != ENOENT) {
	handle->error_num = errno;
	handle->error_msg = strerror(errno);
	return(-1);
  }

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

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

  if ((dbm = dbm_open(naming_context, flags, mode)) == NULL) {
	handle->error_num = errno;
	handle->error_msg = strdup(strerror(errno));
	return(-1);
  }

  h = ALLOC(Handle);
  h->dbmfile = strdup(naming_context);
  h->dbm = dbm;

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

static int
vfs_ndbm_close(Vfs_handle *handle)
{
  Handle *h;

  h = (Handle *) handle->h;
  dbm_close(h->dbm);

  free(h->dbmfile);
  free(h);

  return(0);
}

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

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

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

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

  return(0);
}

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

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

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.dptr = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.dptr = ds_xprintf("%s", key);
  k.dsize = strlen(k.dptr) + handle->null_flag;
  v = dbm_fetch(h->dbm, k);
  if (v.dptr == NULL) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }

  free(k.dptr);
  *length = v.dsize;
  return(0);
}

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

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

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.dptr = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.dptr = ds_xprintf("%s", key);
  k.dsize = strlen(k.dptr) + handle->null_flag;
  v.dptr = NULL;
  v.dsize = 0;
  v = dbm_fetch(h->dbm, k);
  if (v.dptr == NULL) {
	if ((st = dbm_error(h->dbm)) != 0) {
	  handle->error_num = st;
	  handle->error_msg = strdup(strerror(st));
	  dbm_clearerr(h->dbm);
	}
	return(-1);
  }

  free(k.dptr);

  if (buffer != NULL) {
    b = (char *) malloc(v.dsize);
    memcpy(b, v.dptr, v.dsize);
    b[v.dsize] = '\0';
    *buffer = (void *) b;
  }

  if (length != NULL)
	*length = v.dsize;

  return(0);
}

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

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

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.dptr = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.dptr = ds_xprintf("%s", key);
  k.dsize = strlen(k.dptr) + handle->null_flag;
  v.dptr = (char *) buffer;
  v.dsize = length;
  if (dbm_store(h->dbm, k, v, DBM_REPLACE) == -1) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }

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

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

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

  h = (Handle *) handle->h;
  if (handle->sd->item_type != NULL)
	k.dptr = ds_xprintf("%s/%s", handle->sd->item_type, key);
  else
	k.dptr = ds_xprintf("%s", key);
  k.dsize = strlen(k.dptr) + handle->null_flag;
  if (dbm_delete(h->dbm, k) != 0) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }

  return(0);
}

static int
vfs_ndbm_exists(Vfs_handle *handle, char *key)
{

  return(vfs_ndbm_get(handle, key, NULL, NULL));
}

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

  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.dptr = ds_xprintf("%s/%s", handle->sd->item_type, oldkey);
  else
	okey.dptr = ds_xprintf("%s", oldkey);
  okey.dsize = strlen(okey.dptr) + handle->null_flag;

  /* XXX This sequence should be atomic */
  v = dbm_fetch(h->dbm, okey);
  if (v.dptr == NULL) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }

  if (handle->sd->item_type != NULL)
	nkey.dptr = ds_xprintf("%s/%s", handle->sd->item_type, newkey);
  else
	nkey.dptr = ds_xprintf("%s", newkey);
  nkey.dsize = strlen(nkey.dptr) + handle->null_flag;
  if (dbm_store(h->dbm, nkey, v, DBM_REPLACE) != 0) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }
  if (dbm_delete(h->dbm, okey) != 0) {
	st = handle->error_num = dbm_error(h->dbm);
	handle->error_msg = strdup(strerror(st));
	dbm_clearerr(h->dbm);
	return(-1);
  }

  free(okey.dptr);
  free(nkey.dptr);

  return(0);
}

static int
vfs_ndbm_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{
  int n, st;
  size_t nc_len;
  datum k, v;
  Handle *h;

  h = (Handle *) handle->h;

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

  memset(&k, 0, sizeof(k));
  for (k = dbm_firstkey(h->dbm); k.dptr != NULL; k = dbm_nextkey(h->dbm)) {
	memset(&v, 0, sizeof(v));

	v.dptr = NULL;
	v.dsize = 0;
	v = dbm_fetch(h->dbm, k);
	if (v.dptr == NULL) {
	  st = handle->error_num = dbm_error(h->dbm);
	  handle->error_msg = strdup(strerror(st));
	  dbm_clearerr(h->dbm);
	  return(-1);
	}

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

	if (is_valid == NULL || is_valid(k.dptr)) {
	  if (add(handle->sd->item_type, k.dptr, names) == 1)
		n++;
	}
  }

  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_ndbm_init(char *store_name)
{

  return(&vfs_ndbm_conf);
}

#else

Vfs_switch *
vfs_ndbm_init(char *store_name)
{

  return(NULL);
}

#endif
