/*
 * 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 pseudo store layer.
 * The idea is that it implements some kind of data structure on top
 * of a more elementary store layer.  For example, a Unix password file
 * structure, which essentially has a key/value format within a plain
 * text file, would be implemented using a pseudo store layer on top
 * of the filesystem store layer.  But that same format could just as well
 * be on top of an http-based store layer or a database store layer.
 *
 * This layer works on a cached copy, obtained from the lower layer, which
 * is then explicitly or implicitly flushed back through the lower layer.
 *
 * XXX At present, only the key/value format (kwv) is supported and this
 * interface will need to be generalized to support some customization and
 * other simple formats.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_pseudo.c 2831 2015-08-11 00:33:08Z brachman $";
#endif

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

static const char *log_module_name = "vfs_pseudo";

static int pseudo_open(Vfs_handle *, char *naming_context);
static int pseudo_close(Vfs_handle *handle);
static int pseudo_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int pseudo_get(Vfs_handle *handle, char *key, void **buffer,
				  size_t *length);
static int pseudo_getsize(Vfs_handle *handle, char *key, size_t *length);
static int pseudo_put(Vfs_handle *handle, char *key, void *buffer,
				  size_t length);
static int pseudo_delete(Vfs_handle *handle, char *key);
static int pseudo_exists(Vfs_handle *handle, char *key);
static int pseudo_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int pseudo_list(Vfs_handle *handle, int (*is_valid)(char *),
				   int (*compar)(const void *, const void *),
				   int (*add)(char *, char *, void ***), void ***names);

static Vfs_switch pseudo_conf = {
  "kwv",
  pseudo_open,
  pseudo_close,
  pseudo_control,
  pseudo_get,
  pseudo_getsize,
  pseudo_put,
  pseudo_delete,
  pseudo_exists,
  pseudo_rename,
  pseudo_list
};

typedef struct {
  Vfs_handle *pseudo;		/* This layer's descriptor */
  Vfs_handle *llayer;		/* Lower layer's descriptor */
  char *raw;				/* Raw data buffered to/from the lower layer */
  Kwv *kwv;					/* Parsed raw data */
  int modified;				/* Set if raw data needs to be flushed */
  char *default_field_sep;
} Handle;

static char *
get_field_sep_char(Handle *h)
{
  char *p;

  if ((p = kwv_lookup_value(h->pseudo->sd->options, "field_sep")) == NULL) {
	if ((p = h->default_field_sep) == NULL)
	  p = DEFAULT_FIELD_SEP_CHAR;
  }

  return(p);
}

static int
flush_data(Handle *h)
{
  char *p;

  if (h->modified && h->kwv != NULL) {
	size_t len;

	/* Modified - needs to be written.  */
	p = get_field_sep_char(h);

	if ((h->raw = kwv_buf(h->kwv, *p, 0)) == NULL)
	  len = 0;
	else
	  len = strlen(h->raw);

	if (vfs_put(h->llayer, NULL, h->raw, len) == -1) {
	  if (h->llayer->error_msg != NULL)
		log_msg((LOG_ERROR_LEVEL, "%s", h->llayer->error_msg));
	  return(-1);
	}
	h->modified = 0;
  }
  else if (!h->modified && h->llayer->create_flag) {
	/*
	 * If nothing has been modified but the caller has asked for the
	 * file to be created and it does not exist, call the put operation.
	 * This is needed when an empty file is being created.
	 */
	if (vfs_exists(h->llayer, NULL) == 0
		&& vfs_put(h->llayer, NULL, NULL, 0) == -1) {
	  if (h->llayer->error_msg != NULL)
		log_msg((LOG_ERROR_LEVEL, "%s", h->llayer->error_msg));
	  return(-1);
	}
  }

  return(0);
}

static int
load_and_parse(Handle *h)
{
  Kwv_conf kwv_conf = {
	NULL, NULL, NULL, KWV_CONF_DEFAULT, NULL, 30, NULL, NULL
  };

  kwv_conf.sep = get_field_sep_char(h);

  if (h->modified) {
	if (flush_data(h) == -1)
	  return(-1);
  }

  if (h->raw == NULL) {
	if (vfs_get(h->llayer, NULL, (void **) &h->raw, NULL) == -1) {
	  if (h->llayer->error_num != ENOENT)
		return(-1);
	}
	h->kwv = NULL;
  }

  if (h->kwv == NULL) {
	if (h->raw == NULL || *(h->raw) == '\0')
	  h->kwv = kwv_init(10);
	else {
	  if ((h->kwv = kwv_make_sep(NULL, h->raw, &kwv_conf)) == NULL)
		return(-1);
	}
  }

  /* An existing item should be replaced; no duplicates. */
  kwv_set_mode(h->kwv, "dr");

  return(0);
}

static int
pseudo_open(Vfs_handle *handle, char *naming_context)
{
  char *p;
  Handle *h;
  Vfs_handle *sh;
  Vfs_directive *vd;

  if ((p = strchr(handle->sd->store_name, (int) '-')) == NULL) {
	if (naming_context == NULL
		|| (sh = vfs_open_item_type(naming_context)) == NULL) {
	  handle->error_msg = "pseudo_open: could not open lower layer";
	  return(-1);
	}
  }
  else {
	vd = vfs_init_directive(NULL);
	vd->item_type = ds_xprintf("%s_pseudo", handle->sd->item_type);
	vd->store_name = strdup(p + 1);
	vd->naming_context = naming_context;
	vd->options = NULL;
	if (naming_context == NULL || vfs_open(vd, &sh) == -1)
	  return(-1);
  }

  h = ALLOC(Handle);
  h->pseudo = handle;
  h->llayer = sh;
  h->raw = NULL;
  h->kwv = NULL;
  h->modified = 0;
  h->default_field_sep = NULL;

  handle->h = (void *) h;

  return(0);
}

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

  h = (Handle *) handle->h;

  if (flush_data(h) == -1)
	return(-1);

  if ((st = vfs_close(h->llayer)) == -1) {
	handle->error_num = h->llayer->error_num;
	handle->error_msg = h->llayer->error_msg;
	return(-1);
  }

  strzap(h->raw);
  kwv_free(h->kwv);

  free(h);

  return(0);
}

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

  h = (Handle *) handle->h;

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

  case VFS_FLUSH_DATA:
	return(flush_data(h));
	/*NOTREACHED*/

  case VFS_SET_FIELD_SEP:
	h->default_field_sep = va_arg(ap, char *);
	if (h->default_field_sep != NULL && strlen(h->default_field_sep) != 1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid default field separator"));
	  return(-1);
	}

	/*
	va_start(ap, op);
	va_end(ap);
	*/
	break;

  case VFS_GET_FIELD_SEP:
	pptr = va_arg(ap, char **);
	*pptr = get_field_sep_char(h);
	break;

  default:
	/* A non-terminal layer should pass on ops it doesn't understand. */
	if (h->llayer != NULL)
	  return(vfs_control(h->llayer, op, ap));

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

  return(0);
}

static int
pseudo_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  Handle *h;
  Kwv_pair *v;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if ((v = kwv_lookup(h->kwv, key)) == NULL) {
	handle->error_msg = ds_xprintf("Key '%s' not found by getsize", key);
	return(-1);
  }

  if (v->next != NULL) {
	handle->error_msg = ds_xprintf("Key '%s' has multiple values for getsize",
								   key);
	return(-1);
  }

  *length = strlen(v->val);

  return(0);
}

static int
pseudo_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  char **buf;
  Handle *h;
  Kwv_pair *v;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if ((v = kwv_lookup(h->kwv, key)) == NULL) {
	handle->error_msg = ds_xprintf("Key '%s' not found by get", key);
	return(-1);
  }

  if (v->next != NULL) {
	handle->error_msg = ds_xprintf("Key '%s' has multiple values for get",
								   key);
	return(-1);
  }

  buf = (char **) buffer;
  *buf = strdup(v->val);

  if (length != NULL)
	*length = strlen(v->val);

  return(0);
}

static int
pseudo_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  Handle *h;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if (kwv_add(h->kwv, key, (char *) buffer) == NULL) {
	handle->error_msg = h->kwv->error_msg;
	return(-1);
  }

  h->modified = 1;

  return(0);
}

static int
pseudo_delete(Vfs_handle *handle, char *key)
{
  Handle *h;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if (kwv_delete(h->kwv, key) == -1)
	return(-1);

  h->modified = 1;

  if (flush_data(h) == -1)
	return(-1);
  
  return(0);
}

static int
pseudo_exists(Vfs_handle *handle, char *key)
{
  Handle *h;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if (kwv_lookup(h->kwv, key) == NULL)
	return(0);

  return(1);
}

static int
pseudo_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  Handle *h;
  Kwv_pair *v;

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

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  if ((v = kwv_lookup(h->kwv, oldkey)) == NULL)
	return(-1);

  while (v != NULL) {
	if (kwv_add(h->kwv, newkey, v->val) == NULL)
	  return(-1);
	v = v->next;
  }

  kwv_delete(h->kwv, oldkey);

  h->modified = 1;

  return(0);
}

static int
pseudo_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{
  int n;
  Kwv_iter *iter;
  Kwv_pair *pair;
  Handle *h;

  h = (Handle *) handle->h;

  if (load_and_parse(h) == -1)
	return(-1);

  n = 0;
  iter = kwv_iter_begin(h->kwv, NULL);
  while ((pair = kwv_iter_next(iter)) != NULL) {
	if (is_valid == NULL || is_valid(pair->name)) {
	  if (add(handle->sd->naming_context, pair->name, names) == 1)
		n++;
	}
  }
  kwv_iter_end(iter);

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

  return(&pseudo_conf);
}
