/*
 * Copyright (c) 2003-2016
 * 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.
 ***************************************************************************/

/*
 * Generic virtual filestore (VFS) layer
 * This is:
 * 1) an API to perform operations on stored objects (which may be local
 *    or remote);
 * 2) a command line utility to access the API;
 * 3) an interactive utility to access the API; and
 * 4) web services to access the API.
 *
 * This generic layer together with a collection of store "modules" implements
 * a simple virtual filestore that supports basic operations on objects.
 * The operations are intended to be independent of the underlying storage
 * method (such as a native filesystem or a database), although there may
 * be unavoidable semantic differences in some instances.
 *
 * The major benefit is that the backend storage method can be selected without
 * having to change any application-level code.
 *
 * XXX There are no cross-store operations (e.g., copying or moving an object).
 * XXX Little attempt is made to be efficient; if that becomes a problem,
 * a much more complicated design will likely become necessary.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2016\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs.c 2902 2016-07-05 14:45:35Z brachman $";
#endif

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

static MAYBE_UNUSED const char *log_module_name = "vfs";

#ifndef PROG

/* A list of all run-time enabled methods. */
static Dsvec *dsv_enabled_methods = NULL;

Vfs_directive *
vfs_init_directive(Vfs_directive *ovd)
{
  Vfs_directive *vd;

  if (ovd == NULL)
	vd = ALLOC(Vfs_directive);
  else
	vd = ovd;

  vd->uri = NULL;
  vd->uri_str = NULL;
  vd->store_name = NULL;
  vd->item_type = NULL;
  vd->naming_context = NULL;
  vd->option_str = NULL;
  vd->path = NULL;
  vd->options = NULL;

  return(vd);
}

int
vfs_is_valid_item_type(char *item_type)
{
  char *p;

  if (item_type == NULL || !isalpha((int) item_type[0]))
    return(0);

  for (p = item_type + 1; *p != '\0'; p++) {
    if (!isalnum((int) *p) && *p != '-' && *p != '_')
      return(0);
  }

  return(1);
}

/*
 * Notation for:
 * 1) a simplified VFS directive
 *     
 *  VFS "[userdb]dacs-db:/usr/local/dacs/userdb"
 *
 * 2) specifying VFS methods and contexts without having a predefined
 *    item_type (or Store directive).
 *      store(get, "file:///etc/fstab")
 *      store(get, "file:/etc/fstab")
 *      store(get, "http://localhost/index.html")
 *
 *      store(get, "dacs-url://localhost/index.html")
 *      store(get, "dacs-fs:/usr/local/dacs/certnamemap")
 *      store(get, "dacs-kwv:[passwds]password_file]")
 *      store(get, "dacs-fs:[password_file]/etc/passwd")
 *      store(get, "dacs-db:/etc/fstab.db,somekey")
 *      store(get, "dacs-db:/etc/fstab.db", "somekey")
 *  VFS "[password_file]dacs-fs:/usr/local/dacs/passwd"
 *  VFS "[passwds]dacs-kwv:password_file"
 *
 * URI Syntax:
 * [ "[" item_type "]" ] <scheme> : [ // <authority>] [path] [query] [fragment]
 * where the path, if any, has the format
 *   [naming_context] [ "," rel-path]
 * For names that have a separate key relative to the naming_context,
 * its start is delimited by a comma.
 *
 * These three would be equivalent:
 *   VFS "[certmap]file:/usr/dacs/conf/certnamemap"
 *   VFS "[certmap]file:///usr/dacs/conf/certnamemap"
 *   VFS "[certmap]dacs-fs:/usr/dacs/conf/certnamemap"
 *
 * We would like to recognize the http/https, file, ldap, ftp schemes, and
 * a new "dacs" scheme.
 * Scheme names are case insensitive but the canonical form is lower case.
 *
 * For non-"dacs" schemes it will be assumed that everything necessary to
 * accomplish the store operation has been included in the URI, other than
 * the object, where appropriate.  In a sense they are "raw".
 * This allows conventional URLs to be used in certain circumstances,
 * primarily to retrieve an object.
 * Note that some schemes, such as "file", only implement retrieval ("get").
 *
 * For the "dacs" scheme, the first element of the path component identifies
 * the DACS store method and the remainder specifies the path within that
 * store.  If a naming context is needed, it is given by a NAMING_CONTEXT
 * argument in the query string.  Any other arguments are options specific
 * to the store method and this request (e.g., a password).
 * This form provides an alternate interface to existing virtual filestore
 * operations.
 *
 * See RFC 3986.
 */
Vfs_directive *
vfs_uri_parse(char *uri_str, Vfs_directive *ovd)
{
  char *p, *s;
  Uri *uri;
  Vfs_directive *vd;

  vd = vfs_init_directive(ovd);

  if (uri_str[0] == '[') {
	s = uri_str + 1;
	if ((p = strchr(s, (int) ']')) == NULL)
	  return(NULL);
	vd->item_type = strndup(s, p - s);
	if (!vfs_is_valid_item_type(vd->item_type)) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid item_type: \"%s\"", vd->item_type));
	  return(NULL);
	}
	p++;
  }
  else
	p = uri_str;

  if ((uri = vd->uri = uri_parse(p)) == NULL)
	return(NULL);

  /* The scheme has been canonicalized to lowercase by uri_parse() */
  if (streq(uri->scheme, "file") || streq(uri->scheme, "fs")) {
	if ((uri->host != NULL && strcaseeq(uri->host, "localhost"))
		|| uri->port_given != NULL)
	  return(NULL);
	vd->store_name = uri->scheme;
	if (uri->path != NULL) {
	  vd->naming_context = uri->path;
	  if ((p = strchr(vd->naming_context, (int) ',')) != NULL) {
		*p++ = '\0';
		vd->path = p;
	  }
	}
  }
  else if (streq(uri->scheme, "http") || streq(uri->scheme, "https")) {
	vd->store_name = uri->scheme;
	if (uri->path != NULL) {
	  vd->naming_context = uri->uri;
	  if ((p = strchr(vd->naming_context, (int) ',')) != NULL) {
		*p++ = '\0';
		vd->path = p;
	  }
	}
  }
  else if ((vd->store_name = strprefix(uri->scheme, "dacs-")) != NULL) {
	/*
	 * Note: we don't check (or care) if the name is valid, recognized, or
	 * available.
	 */
	if ((p = strsuffix(uri->scheme, strlen(uri->scheme), "-http"))
		!= NULL
		|| (p = strsuffix(uri->scheme, strlen(uri->scheme), "-https")) != NULL)
	  vd->naming_context = strdup(uri->uri + (p - uri->scheme + 1));
	else if (uri->path != NULL)
	  vd->naming_context = strdup(uri->path);

	if (vd->naming_context != NULL) {
	  if ((p = strchr(vd->naming_context, (int) ',')) != NULL) {
		*p++ = '\0';
		vd->path = p;
	  }
	}
  }
  else
	return(NULL);

  if (uri->query_string != NULL) {
	vd->option_str = strdup(uri->query_string);

	/* Parse option string into Kwv */
	if ((vd->options = cgiparse_string(vd->option_str, NULL, NULL)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "Error parsing VFS directive options: %s", uri_str));
	  return(NULL);
	}
  }

  vd->uri_str = strdup(uri_str);

  return(vd);
}

/*
 * Called at initialization time, this function enables all supported
 * backend storage methods.
 * All operations must be defined by a layer, although some may simply
 * return an error or do nothing but return success.
 * Arrange for vfs_term() to be called at termination time.
 * Return 0 if all succeed, -1 otherwise.
 */
void
vfs_init(void)
{
  Vfs_switch *sw;
  Vfs_def *sd;
  static int did_init = 0;

  if (did_init)
	return;
  did_init = 1;

  dsv_enabled_methods = dsvec_init(NULL, sizeof(Vfs_switch *));

  for (sd = &vfs_defs[0]; sd->store_name != NULL; sd++) {
	log_msg((LOG_TRACE_LEVEL, "vfs_init(%s)", sd->store_name));
	if ((sw = (sd->vfs_init)(sd->store_name)) == NULL)
	  continue;

	if (sw->open == NULL || sw->close == NULL || sw->control == NULL
		|| sw->get == NULL || sw->getsize == NULL
		|| sw->put == NULL || sw->delete == NULL || sw->exists == NULL
		|| sw->rename == NULL || sw->list == NULL) {
	  log_msg((LOG_ERROR_LEVEL,
			   "vfs_init: failed for \"%s\"\n", sd->store_name));
	  continue;
	}

	dsvec_add_ptr(dsv_enabled_methods, sw);
  }

  dacs_atexit(vfs_term, NULL);
}

/*
 * Automagically called at application termination time to allow enabled
 * lower layers to clean up (e.g., close file descriptors, free memory,
 * release locks).
 */
void
vfs_term(void *bogus)
{
  int i;
  Vfs_def *sd;
  Vfs_switch *sw;

  for (i = 0; i < dsvec_len(dsv_enabled_methods); i++) {
	sw = (Vfs_switch *) dsvec_ptr_index(dsv_enabled_methods, i);
	for (sd = &vfs_defs[0]; sd->store_name != NULL; sd++) {
	  if (streq(sd->store_name, sw->store_name))
		break;
	}

	if (sd != NULL && sd->vfs_term != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "vfs_term(%s)", sd->store_name));
	  (sd->vfs_term)(sd->store_name);
	}
  }
}

/*
 * Return 1 if STORE_NAME has been enabled, 0 otherwise.
 */
int
vfs_enabled(char *store_name)
{
  int i;
  Vfs_switch *sw;

  for (i = 0; i < dsvec_len(dsv_enabled_methods); i++) {
	sw = (Vfs_switch *) dsvec_ptr_index(dsv_enabled_methods, i);
	if (streq(sw->store_name, store_name))
	  return(1);
  }

  return(0);
}

Dsvec *
vfs_enabled_list(void)
{
  int i;
  Dsvec *dsv;

  dsv = dsvec_init(NULL, sizeof(char *));

  for (i = 0; i < dsvec_len(dsv_enabled_methods); i++) {
	Vfs_switch *sw;

	sw = (Vfs_switch *) dsvec_ptr_index(dsv_enabled_methods, i);
	dsvec_add_ptr(dsv, strdup(sw->store_name));
  }

  return(dsv);
}

static Vfs_op_tab op_tab[] = {
  { VFS_OPEN,    "open" },
  { VFS_CLOSE,   "close" },
  { VFS_CONTROL, "control" },
  { VFS_GET,     "get" },
  { VFS_GETSIZE, "getsize" },
  { VFS_PUT,     "put" },
  { VFS_DELETE,  "delete" },
  { VFS_EXISTS,  "exists" },
  { VFS_RENAME,  "rename" },
  { VFS_LIST,    "list" },
  { VFS_ENABLED, "enabled" },
  { VFS_DEFINED, "defined" },
  { VFS_URI,     "uri" },
  { VFS_UNKNOWN, NULL }
};

Vfs_op
vfs_lookup_op(char *opname)
{
  int i;

  for (i = 0; op_tab[i].op != VFS_UNKNOWN; i++) {
	if (strcaseeq(op_tab[i].opname, opname))
	  return(op_tab[i].op);
  }

  return(VFS_UNKNOWN);
}

/*
 * Return a list of all defined item type definitions, or NULL if an error
 * occurs.
 */
Dsvec *
vfs_list_item_types(Dsvec *odsv)
{
  int i;
  Dsvec *dsv;
  Kwv_pair *v;

  if (odsv != NULL)
	dsv = odsv;
  else
	dsv = dsvec_init(NULL, sizeof(char *));

  if ((v = conf_var(CONF_VFS)) == NULL)
	return(NULL);

  while (v != NULL) {
	dsvec_add_ptr(dsv, strdup(v->val));
	v = v->next;
  }

  return(dsv);
}

/*
 * Search the configuration for a VFS directive for ITEM_TYPE.
 * A VFS directive for the item type "default" establishes a default
 * (there may only be one default).
 * A config file name of "" means there is no config file, and
 * CONF_FILE will be set to NULL.
 * If found, set VD to the parsed directive and return 1;
 * otherwise return -1.
 */
Vfs_directive *
vfs_lookup_item_type(char *item_type)
{
  Kwv_pair *v;
  Kwv_pair *default_v;
  Vfs_directive *vd;

  if (!vfs_is_valid_item_type(item_type)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid item_type: \"%s\"", item_type));
	return(NULL);
  }

  if ((v = conf_var(CONF_VFS)) == NULL)
	return(NULL);

  vd = vfs_init_directive(NULL);

  default_v = NULL;
  while (v != NULL) {
	if (vfs_uri_parse(v->val, vd) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Error parsing VFS directive: %s", v->val));
	  return(NULL);
	}

	if (vd->item_type != NULL) {
	  if (streq(item_type, vd->item_type))
		return(vd);

	  if (default_v == NULL && streq(vd->item_type, "default"))
		default_v = v;
	}

	v = v->next;
  }

  if (default_v == NULL) {
	log_msg((LOG_INFO_LEVEL, "Item type \"%s\" not found in VFS directives",
			 item_type));
	return(NULL);
  }

  if (vfs_uri_parse(default_v->val, vd) == NULL)
	return(NULL);

  return(vd);
}

static char *
CTX(Vfs_handle *h)
{

  if (h->sd->naming_context == NULL)
	return("NULL");
  else
	return(h->sd->naming_context);
}

static const Vfs_conf vfs_conf_default = { 1, 1, 0, 0, 0, 0 };
static Vfs_conf vfs_conf_current = { 1, 1, 0, 0, 0, 0 };

/*
 * Configure VFS defaults if CONF is non-NULL, otherwise return a copy of
 * the current configuration.
 *
 * XXX it should be possible to specify these when vfs_open() et. al are called
 */
Vfs_conf *
vfs_conf(Vfs_conf *conf)
{
  Vfs_conf *c;

  if (conf == NULL) {
	c = ALLOC(Vfs_conf);
	*c = vfs_conf_current;
  }
  else {
	Ds ds;

	vfs_conf_current.append_flag = conf->append_flag;
	vfs_conf_current.create_flag = conf->create_flag;
	vfs_conf_current.delete_flag = conf->delete_flag;
	vfs_conf_current.lock_flag = conf->lock_flag;
	vfs_conf_current.null_flag = conf->null_flag;
	vfs_conf_current.mode = conf->mode;
	c = conf;

	ds_init(&ds);
	ds_asprintf(&ds, "Setting current VFS flags:");
	if (vfs_conf_current.append_flag)
	  ds_asprintf(&ds, " +append");
	if (vfs_conf_current.create_flag)
	  ds_asprintf(&ds, " +create");
	if (vfs_conf_current.delete_flag)
	  ds_asprintf(&ds, " +delete");
	if (vfs_conf_current.null_flag)
	  ds_asprintf(&ds, " +null");
	if (vfs_conf_current.mode != 0)
	  ds_asprintf(&ds, " +mode%o", vfs_conf_current.mode);
	if (vfs_conf_current.lock_flag == VFS_SHLOCK)
	  ds_asprintf(&ds, " +SHLOCK");
	else if (vfs_conf_current.lock_flag == VFS_EXLOCK)
	  ds_asprintf(&ds, " +EXLOCK");
	log_msg((LOG_DEBUG_LEVEL, "%s", ds_buf(&ds)));
  }

  return(c);
}

Vfs_handle *
vfs_open_uri(char *vfs_uri)
{
  Vfs_directive *vd;
  Vfs_handle *h;

  if ((vd = vfs_uri_parse(vfs_uri, NULL)) == NULL)
	return(NULL);

  if (vfs_open(vd, &h) == -1) {
	log_msg((LOG_DEBUG_LEVEL, "Can't open vfs_uri \"%s\"", vfs_uri));
	return(NULL);
  }

  return(h);
}

Vfs_handle *
vfs_open_item_type(char *item_type)
{
  Vfs_directive *vd;
  Vfs_handle *handle;

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	log_msg((LOG_INFO_LEVEL, "Lookup for item type \"%s\" failed", item_type));
	return(NULL);
  }

  if (vfs_open(vd, &handle) == -1) {
	log_msg((LOG_ERROR_LEVEL,
			 "vfs_open_item_type: name \"%s\", item type \"%s\" failed",
			 vd->store_name, item_type));
	if (handle != NULL && handle->error_msg != NULL)
	  log_msg((LOG_ERROR_LEVEL,
			   "vfs_open_item_type: %s", handle->error_msg));
	return(NULL);
  }

  return(handle);
}

/*
 * If ANY looks like a vfs_uri, open it as one.
 * If ANY looks like an item type, open it as one.
 * If ANY looks like a path (starts with a slash), map it to a
 * a file scheme vfs_uri and use that.
 */
Vfs_handle *
vfs_open_any(char *any)
{
  char *uri;
  Vfs_directive *vd;
  Vfs_handle *h;

  if (any == NULL)
	return(NULL);

#ifdef NOTDEF
  /* XXX Is it a good idea to grok relative directories? */
  if (*any == '/'
	  || (*any == '.' && *(any + 1) == '\0')
	  || (*any == '.' && *(any + 1) == '/')
	  || (*any == '.' && *(any + 1) == '.' && *(any + 2) == '\0')
	  || (*any == '.' && *(any + 1) == '.' && *(any + 2) == '/'))
	uri = ds_xprintf("file://%s", any);
#else
  if (*any == '/')
	uri = ds_xprintf("file://%s", any);
#endif
  else
	uri = any;

  if ((vd = vfs_uri_parse(uri, NULL)) != NULL) {
	if ((h = vfs_open_uri(uri)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Could not open \"%s\"", uri));
	  return(NULL);
	}
	return(h);
  }

  /* It's not a DACS URI, so assume it's an item type. */
  if ((vd = vfs_lookup_item_type(uri)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Could not open item type \"%s\"", uri));
	return(NULL);
  }

  if (vfs_open(vd, &h) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Could not open Rlink from \"%s\"", uri));
	return(NULL);
  }

  return(h);
}

int
vfs_get_any(char *any, char *key, void **buffer, size_t *length)
{
  int st;
  Vfs_handle *h;

  if ((h = vfs_open_any(any)) == NULL)
	return(-1);

  st = vfs_get(h, key, buffer, length);
  /* XXX any error info gets lost when it's closed... */
  vfs_close(h);

  return(st);
}

/*
 * Open and initialize the backend storage according to directives SD.
 * Depending on the underlying storage
 * method, vd->naming_context might be the name of a database or a directory.
 * Always set HANDLE to identify the store for subsequent operations, but
 * return 0 if ok and return -1 on error (with an error message and possibly
 * an error number).
 */
int
vfs_open(Vfs_directive *vd, Vfs_handle **handlep)
{
  int i, st;
  Vfs_handle *handle;
  Vfs_switch *sw;

  handle = *handlep = ALLOC(Vfs_handle);
  handle->error_num = 0;
#ifdef NOTDEF
  if (vd->item_type == NULL) {
	handle->error_msg = "Missing item_type argument";
	return(-1);
  }
#endif
  if (vd->store_name == NULL) {
	handle->error_msg = "Missing store_name argument";
	return(-1);
  }

  handle->sd = vd;

  handle->sw = NULL;
  handle->error_msg = NULL;
  handle->error_num = 0;
  handle->get_filter = NULL;
  handle->put_filter = NULL;
  handle->list_sort = NULL;
  handle->kwv = NULL;

  /*
   * Copy the "current" flags to the descriptor, then reset the current
   * flags to the default values.
   * XXX this is a kludge until a better way becomes apparent.
   */
  handle->append_flag = vfs_conf_current.append_flag;
  handle->create_flag = vfs_conf_current.create_flag;
  handle->delete_flag = vfs_conf_current.delete_flag;
  handle->lock_flag = vfs_conf_current.lock_flag;
  handle->null_flag = vfs_conf_current.null_flag;
  handle->mode = vfs_conf_current.mode;

  vfs_conf_current = vfs_conf_default;

  handle->h = NULL;

  sw = NULL;
  for (i = 0; i < dsvec_len(dsv_enabled_methods); i++) {
	sw = (Vfs_switch *) dsvec_ptr_index(dsv_enabled_methods, i);
	if (streq(sw->store_name, vd->store_name)
		|| strprefix(vd->store_name, sw->store_name) != NULL)
	  break;
  }

  if (sw == NULL) {
	handle->error_msg = ds_xprintf("Store name \"%s\" not found",
								   vd->store_name);
	return(-1);
  }
  handle->sw = sw;

  log_msg((LOG_TRACE_LEVEL, "vfs_open(%s,%s)", vd->store_name, CTX(handle)));

  st = (handle->sw->open)(handle, handle->sd->naming_context);

  return(st);
}

/*
 * Close the backend store identified by HANDLE and invalidate HANDLE.
 */
int
vfs_close(Vfs_handle *handle)
{
  int st;

  handle->error_msg = NULL;
  handle->error_num = 0;

  log_msg((LOG_TRACE_LEVEL,
		   "vfs_close(%s,%s)", handle->sd->store_name, CTX(handle)));
  if ((st = (handle->sw->close)(handle)) != 0) {
	/* Return failure but don't destroy the handle. */
	return(-1);
  }

  handle->h = NULL;
  if (handle->kwv != NULL)
	kwv_free(handle->kwv);
  free(handle->sd);
  free(handle);

  return(st);
}

int
vfs_control(Vfs_handle *handle, Vfs_control_op op, ...)
{
  int st;
  va_list ap;

  handle->error_msg = NULL;
  handle->error_num = 0;
  va_start(ap, op);
  log_msg((LOG_TRACE_LEVEL, "vfs_control(%s, %s,%u)",
		   handle->sd->store_name, CTX(handle), op));
  st = (handle->sw->control)(handle, op, ap);
  va_end(ap);

  return(st);
}

/*
 * Read the data item identified by KEY within the given backend store.
 * Memory is allocated for the item and BUFFER is set to point to it.
 * The caller is responsible for freeing this memory.
 * A null byte is appended to the data item.
 * LENGTH, if not NULL, is set to the size of the data item, in bytes.
 */
int
vfs_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int st;
  void *buf;
  size_t len;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_get(%s,%s,\"%s\")",
		   handle->sd->store_name, CTX(handle), key == NULL ? "" : key));

  if ((handle->sw->get)(handle, key, &buf, &len) == -1)
	return(-1);

  st = 0;
  if (handle->get_filter != NULL) {
	void *outbuf;
	size_t outbuf_length;

	log_msg((LOG_TRACE_LEVEL, "get_filter(%s,%s)", CTX(handle), key));
	st = (handle->get_filter)(handle, buf, len, &outbuf, &outbuf_length);
	if (st == -1) {
	  free(buf);
	  return(-1);
	}
	if (st != 0) {
	  free(buf);
	  buf = outbuf;
	  len = outbuf_length;
	}
  }

  *buffer = buf;
  if (length != NULL)
	*length = len;

  return(st);
}

/*
 * Determine the size in bytes of the data item identified by KEY and set
 * LENGTH to it.
 */
int
vfs_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_getsize(%s,%s,\"%s\")",
		   handle->sd->store_name, CTX(handle), key == NULL ? "" : key));

  st = (handle->sw->getsize)(handle, key, length);

  return(st);
}

/*
 * Store the data item in BUFFER that is LENGTH bytes long, using KEY
 * to identify it for future operations.
 * We do not promise that this is atomic, so it's possible for a
 * replace type of operation to fail causing the original value to be lost.
 * Not all underlying store implementations will be capable of performing
 * atomic operations.
 */
int
vfs_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int do_free, st;
  void *buf, *outbuf;
  size_t len, outbuf_length;

  handle->error_msg = NULL;
  handle->error_num = 0;

  buf = buffer;
  len = length;
  do_free = 0;

  if (handle->put_filter != NULL) {
	log_msg((LOG_TRACE_LEVEL, "put_filter(%s,%s,%s)",
			 handle->sd->store_name, CTX(handle), key));
	st = (handle->put_filter)(handle, buf, len, &outbuf, &outbuf_length);
	if (st == -1)
	  return(-1);
	if (st != 0) {
	  buf = outbuf;
	  len = outbuf_length;
	  do_free = 1;
	}
  }

  log_msg((LOG_TRACE_LEVEL, "vfs_put(%s,%s,%s,%u)",
		   handle->sd->store_name, CTX(handle), key == NULL ? "NULL" : key,
		   len));
  st = (handle->sw->put)(handle, key, buf, len);

  if (do_free)
	free(outbuf);

  return(st);
}

/*
 * Delete the data item identified by KEY.
 */
int
vfs_delete(Vfs_handle *handle, char *key)
{
  int st;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_delete(%s,%s,%s)",
		   handle->sd->store_name, CTX(handle), key == NULL ? "NULL" : key));

  st = (handle->sw->delete)(handle, key);

  return(st);
}

/*
 * Check for the existence of a data item identified by KEY.
 * Return 0 if it does not exist, 1 if it does, and -1 if we don't know
 * because an error occurred.
 */
int
vfs_exists(Vfs_handle *handle, char *key)
{
  int st;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_exists(%s,%s,%s)",
		   handle->sd->store_name, CTX(handle), key == NULL ? "NULL" : key));

  st = (handle->sw->exists)(handle, key);

  return(st);
}

/*
 * Rename the item identified by OLDKEY to be identified by NEWKEY,
 * which must be different.
 */
int
vfs_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  int st;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_rename(%s,%s,%s,%s)",
		   handle->sd->store_name, CTX(handle), oldkey, newkey));

  if (streq(oldkey, newkey))
	return(-1);

  st = (handle->sw->rename)(handle, oldkey, newkey);

  return(st);
}

/*
 * Create a list of keys, pointed to by NAMES.
 * A name is added to the list if IS_VALID() returns non-zero when passed the
 * key.  If not NULL, COMPAR() is an ordering function used to sort the
 * keys (it works like the function passed to qsort(3)).
 * ADD is a function that is called with a name and the NAMES argument to add
 * the given name to the list of names; it must return 1 if the item was
 * actually added to the list (it may choose to ignore the item), 0 otherwise.
 * The list of names is terminated by a NULL entry.  The caller is
 * responsible for freeing memory allocated by ADD().
 * The number of names is returned, or -1 on error.
 */
int
vfs_list(Vfs_handle *handle,
		   int (*is_valid)(char *),
		   int (*compar)(const void *, const void *),
		   int (*add)(char *, char *, void ***), void ***names)
{
  int n;

  handle->error_msg = NULL;
  handle->error_num = 0;
  log_msg((LOG_TRACE_LEVEL, "vfs_list(%s,%s)",
		   handle->sd->store_name, CTX(handle)));

  n = (handle->sw->list)(handle, is_valid, compar, add, names);

  return(n);
}

/*
 * This is the command line utility and web service.
 * If no operation is given on the command line, the program goes into
 * interactive mode.
 */

#include "dacs.h"

static int is_interactive = 1;
static char *field_sep_char = DEFAULT_FIELD_SEP_CHAR;
static DACS_app_type app_type;

static int
list_add(char *naming_context, char *name, void ***ptr)
{
  char *p;
  Dsvec *dsv;

  if ((dsv = (Dsvec *) *ptr) == NULL) {
	*ptr = (void **) dsvec_init(NULL, sizeof(char *));
	dsv = (Dsvec *) *ptr;
  }

  if (naming_context != NULL && (p = strprefix(name, naming_context)) != NULL)
	dsvec_add_ptr(dsv, strdup(p + 1));
  else
	dsvec_add_ptr(dsv, strdup(name));

  return(1);
}

static int
vfss_list_enabled(void)
{
  int i;
  Vfs_switch *sw;

  if (app_type == DACS_WEB_SERVICE)
	printf("\n");

  for (i = 0; i < dsvec_len(dsv_enabled_methods); i++) {
	sw = (Vfs_switch *) dsvec_ptr_index(dsv_enabled_methods, i);
	printf("%s\n", sw->store_name);
  }

  return(0);
}

static int
vfss_get(Vfs_handle *handle, char *key, char **errmsg)
{
  char *ptr;
  size_t length;
  ssize_t nwritten;

  if (vfs_get(handle, key, (void *) &ptr, &length) == -1)
	return(-1);

  if (app_type == DACS_WEB_SERVICE)
	nwritten = write(1, "\n", 1);

  nwritten = write(1, ptr, length);
  if (key != NULL)
	nwritten = write(1, "\n", 1);

  return(0);
}

static int
do_update(Vfs_handle *handle, char *key, char **ermsg)
{
  int st;
  char *tempfile, *new_ptr, *ptr;
  size_t length, new_length, nwritten;
  FILE *fp;

  if (vfs_get(handle, key, (void *) &ptr, &length) == -1)
	return(-1);

  tempfile = create_temp_filename(NULL);
  if ((fp = create_temp_file(tempfile)) == NULL) {
	fprintf(stderr, "do_update: can't create temp file \"%s\"\n", tempfile);
	return(-1);
  }

  if ((nwritten = fwrite(ptr, 1, length, fp)) != length) {
	if (ferror(fp))
	  fprintf(stderr, "do_update: fwrite failed: %s\n", strerror(errno));
	else
	  fprintf(stderr, "do_update: fwrite did not return correct byte count\n");
	if (unlink(tempfile) == -1)
	  fprintf(stderr, "do_update: error unlinking tempfile \"%s\": %s\n",
			  tempfile, strerror(errno));
	return(-1);
  }

  fclose(fp);
  memzap(ptr, length);

  if (edit_file(tempfile) != 1) {
	fprintf(stderr, "do_update: aborted\n");
	if (unlink(tempfile) == -1)
	  fprintf(stderr, "do_update: error unlinking tempfile \"%s\": %s\n",
			  tempfile, strerror(errno));
	return(-1);
  }

  if (load_file(tempfile, &new_ptr, &new_length) == -1) {
	fprintf(stderr, "do_update: error reading temp file\n");
	if (unlink(tempfile) == -1)
	  fprintf(stderr, "do_update: error unlinking tempfile \"%s\": %s\n",
			  tempfile, strerror(errno));
	return(-1);
  }

  st = 0;
  if (vfs_put(handle, key, new_ptr, new_length) == -1) {
	if (handle->error_msg != NULL)
	  fprintf(stderr, "vfs_update: %s\n", handle->error_msg);
	else
	  fprintf(stderr, "vfs_update: failed\n");
	st = -1;
  }
  else
	printf("ok\n");

  memzap(new_ptr, new_length);
  if (unlink(tempfile) == -1) {
	fprintf(stderr, "do_update: error unlinking tempfile \"%s\": %s\n",
			tempfile, strerror(errno));
	st = -1;
  }

  return(st);
}

static int
vfss_getsize(Vfs_handle *handle, char *key, char **ermsg)
{
  size_t length;

  if (vfs_getsize(handle, key, &length) == -1)
	return(-1);
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\n");
	printf("%u bytes\n", (unsigned int ) length);
  }

  return(0);
}

static int
vfss_delete(Vfs_handle *handle, char *key, char **ermsg)
{

  if (vfs_delete(handle, key) == -1)
	return(-1);
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\nok\n");
  }

  return(0);
}

static int
vfss_exists(Vfs_handle *handle, char *key, char **ermsg)
{
  int st;

  if ((st = vfs_exists(handle, key)) == -1)
	return(-1);

  if (st == 1) {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\nyes\n");
  }
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\nno\n");
  }

  return(0);
}

static int
vfss_put(Vfs_handle *handle, char *key, char *value, char **ermsg)
{
  int n;

  if (value == NULL) {
	Ds *ds = ds_init(NULL);

	if (is_interactive)
	  fprintf(stderr, "[Enter the value now, type EOF when done]\n");

	n = 0;
	while (ds_agets(ds, stdin) != NULL)
	  n++;

	if (n == 1)
	  value = strtrim(ds_buf(ds), "\n", 0);
	else
	  value = ds_buf(ds);
	  
	clearerr(stdin);
  }

  if (vfs_put(handle, key, value, strlen(value)) == -1)
	return(-1);
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\nok\n");
  }

  return(0);
}

static int
vfss_dump(Vfs_handle *handle, char **errmsg)
{
  int i, st;
  Dsvec *names;

  names = NULL;
  st = vfs_list(handle, NULL, NULL, list_add, (void ***) &names);
  if (st == -1)
	return(-1);

  if (app_type == DACS_WEB_SERVICE)
	printf("\n");

  for (i = 0; i < dsvec_len(names); i++) {
	char *key, *value;
	size_t length;

	key = (char *) dsvec_ptr_index(names, i);
	if (vfs_get(handle, key, (void *) &value, &length) == -1)
	  return(-1);
	printf("%s%s%s\n", key, field_sep_char, value);
  }

  return(0);
}

static int
vfss_load(Vfs_handle *handle, char *value, char **errmsg)
{
  char *val;
  Ds *ds;
  Kwv *kwv;
  Kwv_iter *iter;
  Kwv_pair *v;
  Kwv_conf kwv_conf = {
	NULL, NULL, NULL, KWV_CONF_DEFAULT, NULL, 30, NULL, NULL
  };

  kwv_conf.sep = field_sep_char;
  if (*kwv_conf.sep == ' ' || *kwv_conf.sep == '\t')
	kwv_conf.ws_pad_chars = NULL;
  else
	kwv_conf.ws_pad_chars = " \t";

  ds = ds_init(NULL);
  if (value == NULL) {

	if (is_interactive)
	  fprintf(stderr, "[Enter the value now, type EOF when done]\n");

	while (ds_agets(ds, stdin) != NULL)
	  ;
	if (ferror(stdin) || !feof(stdin))
	  return(-1);

	clearerr(stdin);
	val = ds_buf(ds);
  }
  else
	val = value;

  if ((kwv = kwv_make_sep(NULL, val, &kwv_conf)) == NULL)
	return(-1);

  iter = kwv_iter_begin(kwv, NULL);
  for (v = kwv_iter_first(iter); v != NULL; v = kwv_iter_next(iter)) {
	if (vfs_put(handle, v->name, v->val, strlen(v->val)) == -1)
	  return(-1);
  }
  kwv_iter_end(iter);

  if (app_type == DACS_WEB_SERVICE)
	printf("\nok\n");

  return(0);
}

static int
vfss_rename(Vfs_handle *handle, char *oldkey, char *newkey, char **ermsg)
{

  if (vfs_rename(handle, oldkey, newkey) == -1)
	return(-1);
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\nok\n");
  }

  return(0);
}

static int
vfss_list(Vfs_handle *handle,  int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), char **ermsg)
{
  int i, st;
  Dsvec *names;

  names = NULL;
  st = vfs_list(handle, is_valid, compar, add, (void ***) &names);
  if (st == -1)
	return(-1);
  else {
	if (app_type == DACS_WEB_SERVICE)
	  printf("\n");
	for (i = 0; i < dsvec_len(names); i++)
	  printf("%s\n", (char *) dsvec_ptr_index(names, i));
  }

  return(0);
}

/*
 * Perform a VFS web service.
 */
static int
vfss(Kwv *kwv, char **errmsg)
{
  int i, n, st;
  char **argv, *item_type, *jurisdiction, *p, *request, *sep, *value, *version;
  Vfs_conf *vfs_config;
  Vfs_directive *vd;
  Vfs_handle *handle;
  Mkargv conf = { 0, 0, NULL, NULL, NULL };

  /* Look up the arguments. */
  item_type = kwv_lookup_value(kwv, "ITEM_TYPE");
  jurisdiction = kwv_lookup_value(kwv, "DACS_JURISDICTION");
  request = kwv_lookup_value(kwv, "REQUEST");
  value = kwv_lookup_value(kwv, "VALUE");
  version = kwv_lookup_value(kwv, "DACS_VERSION");
  sep = kwv_lookup_value(kwv, "FIELD_SEP");

  vfs_config = vfs_conf(NULL);
  vfs_config->null_flag = (p = kwv_lookup_value(kwv, "NULL_FLAG")) != NULL
	&& streq(p, "1");
  vfs_config->create_flag = (p = kwv_lookup_value(kwv, "CREATE_FLAG")) != NULL
	&& streq(p, "1");
  vfs_config->delete_flag = (p = kwv_lookup_value(kwv, "DELETE_FLAG")) != NULL
	&& streq(p, "1");
  vfs_config->append_flag = (p = kwv_lookup_value(kwv, "APPEND_FLAG")) != NULL
	&& streq(p, "1");

  if ((p = kwv_lookup_value(kwv, "MODE")) != NULL) {
	if (strnum_b(p, STRNUM_MODE_T, 8, &vfs_config->mode, NULL) == -1)
	  vfs_config->mode = 0;
  }
  if ((p = kwv_lookup_value(kwv, "LOCK_FLAG")) != NULL) {
	int val;

	if (strnum(p, STRNUM_I, &val) != -1) {
	  if (val == VFS_SHLOCK || val == VFS_EXLOCK)
		vfs_config->lock_flag = val;
	}
  }
  vfs_conf(vfs_config);

  st = 0;
  handle = NULL;

  if (item_type == NULL) {
	if (errmsg != NULL)
	  *errmsg = "Missing ITEM_TYPE argument";
	log_msg((LOG_ERROR_LEVEL, "Missing ITEM_TYPE argument"));
	st = -1;
	goto fail;
  }

  if (!vfs_is_valid_item_type(item_type)) {
	log_msg((LOG_ERROR_LEVEL, "Invalid item_type: \"%s\"", item_type));
	if (errmsg != NULL)
	  *errmsg = "Invalid item_type";
	st = -1;
	goto fail;
  }

  if (strcaseeq(item_type, "enabled")) {
	vfss_list_enabled();
	return(0);
  }

  if (request == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Missing REQUEST argument"));
	if (errmsg != NULL)
	  *errmsg = "Missing REQUEST argument";
	st = -1;
	goto fail;
  }
  
  if ((n = mkargv(request, &conf, &argv)) <= 0) {
	log_msg((LOG_ERROR_LEVEL, "Invalid REQUEST argument"));
	if (errmsg != NULL)
	  *errmsg = "Invalid REQUEST argument";
	st = -1;
	goto fail;
  }

  if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "VFS directives are missing or invalid"));
	if (errmsg != NULL)
	  *errmsg = "VFS directives are missing or invalid";
	st = -1;
	goto fail;
  }

  if (vfs_open(vd, &handle) == -1) {
	log_msg((LOG_ERROR_LEVEL, "vfs_open failed"));
	if (errmsg != NULL)
	  *errmsg = "vfs_open failed";
	st = -1;
	goto fail;
  }

  if (sep != NULL) {
	if (strlen(sep) != 1) {
	  if (errmsg != NULL)
		*errmsg = "Invalid field separator";
	  st = -1;
	  goto fail;
	}
	field_sep_char = sep;

	if (vfs_control(handle, VFS_SET_FIELD_SEP, sep) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot set field separator"));
	  if (errmsg != NULL)
		*errmsg = "vfs_control failed";
	  st = -1;
	  goto fail;
	}
  }

  i = 0;
  if (strcaseeq(argv[i], "enabled"))
	st = vfss_list_enabled();
  else if (strcaseeq(argv[i], "get")) {
	if (argv[i + 1] == NULL)
	  st = vfss_get(handle, NULL, errmsg);
	else if (argv[i + 2] != NULL)
	  st = -1;
	else
	  st = vfss_get(handle, argv[i + 1], errmsg);
  }
  else if (strcaseeq(argv[i], "getsize")) {
	if (argv[i + 1] == NULL)
	  st = vfss_getsize(handle, NULL, errmsg);
	else if (argv[i + 2] != NULL)
	  st = -1;
	else
	  st = vfss_getsize(handle, argv[i + 1], errmsg);
  }
  else if (strcaseeq(argv[i], "put")) {
	if (value == NULL)
	  st = -1;
	else if (argv[i + 1] == NULL)
	  st = vfss_put(handle, NULL, value, errmsg);
	else if (argv[i + 2] != NULL)
	  st = -1;
	else
	  st = vfss_put(handle, argv[i + 1], value, errmsg);
  }
  else if (strcaseeq(argv[i], "load")) {
	if (argv[i + 1] == NULL)
	  st = vfss_load(handle, value, errmsg);
	else
	  st = -1;
  }
  else if (strcaseeq(argv[i], "dump")) {
	if (argv[i + 1] == NULL)
	  st = vfss_dump(handle, errmsg);
	else
	  st = -1;
  }
  else if (strcaseeq(argv[i], "delete")) {
	if (argv[i + 1] == NULL)
	  st = vfss_delete(handle, NULL, errmsg);
	else if (argv[i + 2] != NULL)
	  st = -1;
	else
	  st = vfss_delete(handle, argv[i + 1], errmsg);
  }
  else if (strcaseeq(argv[i], "exists")) {
	if (argv[i + 1] == NULL)
	  st = vfss_exists(handle, NULL, errmsg);
	else if (argv[i + 2] != NULL)
	  st = -1;
	else
	  st = vfss_exists(handle, argv[i + 1], errmsg);
  }
  else if (strcaseeq(argv[i], "rename")) {
	if (argv[i + 1] != NULL && argv[i + 2] == NULL)
	  st = vfss_rename(handle, NULL, argv[i + 1], errmsg);
	else if (argv[i + 1] != NULL && argv[i + 2] != NULL
			 && argv[i + 3] == NULL)
	  st = vfss_rename(handle, argv[i + 1], argv[i + 2], errmsg);
	else
	  st = -1;
  }
  else if (streq(argv[i], "list")) {
	if (argv[i + 1] != NULL)
	  st = -1;
	else
	  st = vfss_list(handle, NULL, NULL, list_add, errmsg);
  }
  else {
	log_msg((LOG_ERROR_LEVEL, "Unrecognized command: %s\n", argv[i]));
	st = -1;
  }

 fail:
  if (st == -1) {
	if (handle != NULL && handle->error_msg != NULL) {
	  log_msg((LOG_ERROR_LEVEL, "%s", handle->error_msg));
	  if (errmsg != NULL && *errmsg == NULL)
		*errmsg = strdup(handle->error_msg);
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Request failed"));
	  if (errmsg != NULL && *errmsg == NULL)
		*errmsg = "Request failed";
	}
  }

  if (handle != NULL && vfs_close(handle) == -1) {
	log_msg((LOG_ERROR_LEVEL, "vfs_close failed"));
	st = -1;
  }

  return(st);
}

static void
show_usage(void)
{

  if (dacs_quiet_flag)
	exit(1);

  fprintf(stderr, "Usage:\n");
  fprintf(stderr, "dacsvfs %s [-F sep] item-type | vfs_uri [op [arg ...]]\n",
		  standard_command_line_usage);
  fprintf(stderr, "Where op is:\n");
  fprintf(stderr, "  delete [key]:  delete an item\n");
  fprintf(stderr, "  dump:          write key:value lines to stdout\n");
  fprintf(stderr, "  edit [key]:    interactively edit an item\n");
  fprintf(stderr, "  enabled:       display enabled store methods\n");
  fprintf(stderr, "  exists [key]:  test whether an item exists\n");
  fprintf(stderr, "  get [key]:     display an item\n");
  fprintf(stderr, "  getsize [key]: display the size of an item\n");
  fprintf(stderr, "  help:          display this usage summary\n");
  fprintf(stderr, "  list:          list all items\n");
  fprintf(stderr, "  load:          load key:value lines from stdin\n");
  fprintf(stderr, "  put [key]:     store or replace an item\n");
  fprintf(stderr, "  putval key val: store or replace an item\n");
  fprintf(stderr, "  rename [key] newkey: rename an item\n");
  fprintf(stderr, "  update [key]:  same as 'edit'\n");

  exit(1);
}

int
dacsvfs_main(int argc, char **argv, int do_init, void *main_out)
{
  int i, st;
  char *item_type, *vfs;
  char *buf, *errmsg;
  Crypt_keys *keys;
  Vfs_directive *vd;
  Vfs_handle *handle;
  Kwv *kwv;
  Ds *ds;

  errmsg = "Internal error";

  if (getenv("REMOTE_ADDR") != NULL) {
    app_type = DACS_WEB_SERVICE;
    log_module_name = "dacs_vfs";
  }
  else {
    app_type = DACS_UTILITY;
    log_module_name = "dacsvfs";
  }

  if (dacs_init(app_type, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (app_type == DACS_WEB_SERVICE) {
	  printf("Status: 400\n");
	  printf("\n");
	  if (errmsg != NULL)
		printf("%s\n", errmsg);
	}
	else {
	  fprintf(stderr, "%s\n", errmsg);
	  show_usage();
	}

	return(-1);
  }

  errmsg = NULL;

  /* This is solely to ensure that the user is an administrator. */
  if ((keys = crypt_keys_from_vfs(ITEM_TYPE_FEDERATION_KEYS)) == NULL) {
    errmsg = "Permission denied";
    goto fail;
  }
  crypt_keys_free(keys);

  if (app_type == DACS_WEB_SERVICE) {
	/* Perform one web service request and then exit. */
	errmsg = NULL;
	if (vfss(kwv, &errmsg) == -1)
	  goto fail;

	return(0);
  }

  /* Everything that follows is only done when this is invoked as a command. */

  if (argv[1] == NULL) {
	fprintf(stderr, "Missing item-type or vfs_uri argument\n");
	show_usage();
	return(-1);
  }

  /* A special case - print enabled store names, then exit. */
  if (streq(argv[1], "enabled")) {
	vfss_list_enabled();
	return(0);
  }

  i = 1;
  if (streq(argv[i], "-F")) {
	if (++i == argc) {
	  fprintf(stderr, "Missing field separator character\n");
	  show_usage();
	  return(-1);
	}
	if (strlen(argv[i]) != 1) {
	  fprintf(stderr, "Field separator must be a single character\n");
	  show_usage();
	  return(-1);
	}

	field_sep_char = strdup(argv[i]);
	i++;
  }

  vfs = NULL;
  item_type = NULL;

  if ((vd = vfs_uri_parse(argv[i], NULL)) != NULL) {
	vfs = argv[i];
	if ((handle = vfs_open_uri(vfs)) == NULL) {
	  fprintf(stderr, "Invalid vfs_uri: \"%s\"\n", vfs);
	  return(-1);
	}
  }
  else {
	item_type = argv[i];
	if ((vd = vfs_lookup_item_type(item_type)) == NULL) {
	  fprintf(stderr, "VFS directives are missing or invalid for \"%s\"\n",
			  item_type);
	  return(-1);
	}

	if (vfs_open(vd, &handle) == -1) {
	  fprintf(stderr, "vfs_open failed for \"%s\"\n", item_type);
	  return(-1);
	}
  }

  if (vfs_control(handle, VFS_SET_FIELD_SEP, field_sep_char) == -1) {
	fprintf(stderr, "Cannot set field separator\n");
	vfs_close(handle);
	return(-1);
  }

  /*
   * If an operation is requested on the command line, do it and then exit.
   */
  i++;
  st = -1;
  if (argv[i] != NULL) {
	is_interactive = isatty(0);
	if (streq(argv[i], "enabled"))
	  st = vfss_list_enabled();
	else if (streq(argv[i], "get")) {
	  if (argv[i + 1] == NULL)
		st = vfss_get(handle, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = vfss_get(handle, argv[i + 1], &errmsg);
	}
	else if (streq(argv[i], "getsize")) {
	  if (argv[i + 1] == NULL)
		st = vfss_getsize(handle, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = vfss_getsize(handle, argv[i + 1], &errmsg);
	}
	else if (streq(argv[i], "put")) {
	  if (argv[i + 1] == NULL)
		st = vfss_put(handle, NULL, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = vfss_put(handle, argv[i + 1], NULL, &errmsg);
	}
	else if (streq(argv[i], "putval")) {
	  if (argv[i + 1] == NULL || argv[i + 2] == NULL || argv[i + 3] != NULL)
		show_usage();
	  st = vfss_put(handle, argv[i + 1], argv[i + 2], &errmsg);
	}
	else if (streq(argv[i], "load")) {
	  if (argv[i + 1] == NULL)
		st = vfss_load(handle, NULL, &errmsg);
	  else
		show_usage();
	}
	else if (streq(argv[i], "dump")) {
	  if (argv[i + 1] == NULL)
		st = vfss_dump(handle, &errmsg);
	  else
		show_usage();
	}
	else if (streq(argv[i], "update")
			 || streq(argv[i], "edit")
			 || streq(argv[i], "ed")) {
	  if (argv[i + 1] == NULL)
		st = do_update(handle, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = do_update(handle, argv[i + 1], &errmsg);
	}
	else if (streq(argv[i], "delete")) {
	  if (argv[i + 1] == NULL)
		st = vfss_delete(handle, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = vfss_delete(handle, argv[i + 1], &errmsg);
	}
	else if (streq(argv[i], "exists")) {
	  if (argv[i + 1] == NULL)
		st = vfss_exists(handle, NULL, &errmsg);
	  else if (argv[i + 2] != NULL)
		show_usage();
	  else
		st = vfss_exists(handle, argv[i + 1], &errmsg);
	}
	else if (streq(argv[i], "rename")) {
	  if (argv[i + 1] != NULL && argv[i + 2] == NULL)
		st = vfss_rename(handle, NULL, argv[i + 1], &errmsg);
	  else if (argv[i + 1] != NULL && argv[i + 2] != NULL
			   && argv[i + 3] == NULL)
		st = vfss_rename(handle, argv[i + 1], argv[i + 2], &errmsg);
	  else
		show_usage();
	}
	else if (streq(argv[i], "list")) {
	  if (argv[i + 1] != NULL)
		show_usage();
	  st = vfss_list(handle, NULL, NULL, list_add, &errmsg);
	}
	else if (streq(argv[i], "help"))
	  show_usage();
	else {
	  fprintf(stderr, "Unrecognized command: %s\n", argv[i]);
	  show_usage();
	  return(-1);
	}

	if (st == -1) {
	  if (handle != NULL && handle->error_msg != NULL)
		fprintf(stderr, "%s\n", handle->error_msg);
	  if (errmsg != NULL)
		fprintf(stderr, "%s\n", errmsg);
	}

	if (vfs_close(handle) == -1) {
	  fprintf(stderr, "vfs_close failed\n");
	  return(-1);
	}

	return(st);
  }

  /*
   * No command line operation was given - enter interactive mode.
   */
  ds = ds_init(NULL);
  ds->delnl_flag = 1;

  is_interactive = 1;
  while (1) {
	int xargc;
	char **xargv;

	printf("> ");
	if ((buf = ds_gets(ds, stdin)) == NULL) {
	  if (feof(stdin) || ferror(stdin))
		break;
	  continue;
	}

	if ((xargc = mkargv(buf, NULL, &xargv)) == -1)
	  goto huh;
	if (xargc == 0)
	  continue;

	st = 0;
	errmsg = NULL;

	if (streq(xargv[0], "quit"))
	  break;
	else if (streq(xargv[0], "getsize")) {
	  if (xargc > 2)
		goto huh;
	  st = vfss_getsize(handle, xargv[1], &errmsg);
	}
	else if (streq(xargv[0], "get")) {
	  if (xargc > 2)
		goto huh;
	  st = vfss_get(handle, xargv[1], &errmsg);
	}
	else if (streq(xargv[0], "update")
			 || streq(xargv[0], "edit")
			 || streq(xargv[0], "ed")) {
	  if (xargc > 2)
		goto huh;
	  st = do_update(handle, xargv[1], &errmsg);
	}
	else if (streq(xargv[0], "delete")) {
	  if (xargc > 2)
		goto huh;
	  st = vfss_delete(handle, xargv[1], &errmsg);
	}
	else if (streq(xargv[0], "exists")) {
	  if (xargc > 2)
		goto huh;
	  st = vfss_exists(handle, xargv[1], &errmsg);
	}
	else if (streq(xargv[0], "put")) {
	  if (xargc < 2 || xargc > 3)
		goto huh;
	  st = vfss_put(handle, xargv[1], xargv[2], &errmsg);
	}
	else if (streq(xargv[0], "load")) {
	  if (xargc > 1)
		goto huh;
	  st = vfss_load(handle, NULL, &errmsg);
	}
	else if (streq(xargv[0], "dump")) {
	  if (xargc > 1)
		goto huh;
	  st = vfss_dump(handle, &errmsg);
	}
	else if (streq(xargv[0], "rename")) {
	  if (xargc < 2 || xargc > 3)
		goto huh;
	  st = vfss_rename(handle, xargv[1], xargv[2], &errmsg);
	}
	else if (streq(buf, "list")) {
	  if (xargc > 1)
		goto huh;
	  st = vfss_list(handle, NULL, NULL, list_add, &errmsg);
	}
	else if (streq(buf, "enabled"))
	  st = vfss_list_enabled();
	else if (strneq(buf, "?", 1) || strneq(buf, "help", 4)) {
	  fprintf(stderr, "Commands:\n");
	  fprintf(stderr, "dump\n");
	  fprintf(stderr, "get [key]\n");
	  fprintf(stderr, "getsize [key]\n");
	  fprintf(stderr, "delete [key]\n");
	  fprintf(stderr, "enabled\n");
	  fprintf(stderr, "exists [key]\n");
	  fprintf(stderr, "help\n");
	  fprintf(stderr, "list\n");
	  fprintf(stderr, "load\n");
	  fprintf(stderr, "put [key] <value>\n");
	  fprintf(stderr, "rename [oldkey] <newkey>\n");
	  fprintf(stderr, "?\n");
	}
	else {
	huh:
	  fprintf(stderr, "huh?\n");
	}

	if (st == -1) {
	  if (errmsg != NULL)
		fprintf(stderr, "%s\n", errmsg);
	}
  }
  ds_free(ds);

  if (vfs_close(handle) == -1) {
	fprintf(stderr, "vfs_close failed\n");
	return(-1);
  }

  return(0);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsvfs_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
