/*
 * 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.
 ***************************************************************************/

/*
 * Dynamic string package
 * Two primary types of functionality are provided:
 * 1) Functions on arbitrary length, dynamically allocated strings.
 *    In particular, safe replacements for sprintf() are included
 *    (formatted output goes into a dynamically allocated buffer that is
 *    guaranteed to be big enough).  These strings need not be null-terminated.
 *    Buffers are automatically extended as necessary.
 *    The names of these functions are prefixed with "ds_".
 * 2) Functions to manipulate arbitrary length vectors of pointers (and
 *    also structures).
 *    The names of these functions are prefixed with "dsvec_".
 *
 * These functions are critical to avoiding statically-allocated buffers and
 * buffer overruns and make it easier to ensure that strings are erased
 * and freed when they are no longer needed.
 *
 * NB: the call to the sprintf-like functions must be idempotent (calling
 * twice in a row with the same arguments produces the identical result),
 * which in normal use should always be the case.
 * NB: Be careful about keeping pointers to dynamically allocated buffers
 * since they may become invalid if the buffers are reallocated (if they
 * extend or shrink).
 *
 * XXX it might be useful to be able to name/identify a Dsvec element,
 * independently of its current index within the vector, so that it can be
 * referenced later (e.g., for deletion, for inserting a new item before it or
 * after it, etc.).  Maybe each new element could be assigned a monotonically
 * increasing "serial number", Dsvec_id, and then:
 *     Dsvec_id id = dsvec_get_id(dsv, num);	-- With a distinct error val
 *     num = dsvec_get_index(dsv, id);			-- Or -1 if it doesn't exist
 * This would seem to require maintaining a separate vector of identifiers,
 * since elements can be arbitrarily inserted and deleted, and reallocation
 * can change memory addresses; so there would be an extra memory cost plus
 * the cost of managing the vector of identifiers after insertions and
 * deletions, although appending is going to be the most common operation.
 */

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

/*
 * Ideas:
 * o make the input source for ds_getc(), ds_gets(), ds_agets() configurable
 *   so that it can be set to a buffer, stream, or file descriptor.
 */

#ifndef DSSLIB
#include "dacs_config.h"
#endif

#include "range.h"
#include "str.h"
#include "ds.h"

#include <errno.h>

/* Any function that may set this must always reset it to NULL on entry. */
char *ds_errmsg = NULL;

#ifdef NOTDEF
static inline int
streq(const char *p, const char *q)
{

	return(strcmp(p, q) == 0);
}

/*
 * Make sure the function isn't optimized away because the string is not
 * read after it is written.
 */
static void
memzap(void *s, size_t len)
{
  volatile char *ptr = (volatile char *) s;
  size_t i;

  for (i = 0; i < len; i++)
	*ptr++ = '\0';
}
#endif

/*
 * This is a simple if inefficient way to get the formatted length.
 * XXX change this to use: char buf[1]; vsnprintf(buf, 0, fmt, ap);
 *
 * XXX We assume that fprintf() and sprintf() return the same character
 * counts when given identical arguments.  If the value of an argument is
 * changed by the call to fprintf() so that the output of sprintf() is
 * different than if no call to fprintf() had been made, then these functions
 * may fail.
 * XXX the stream is never closed
 */
static int
get_formatted_length(const char *fmt, va_list ap, size_t *flen)
{
  int st;
  static FILE *fp = NULL;

  if (fp == NULL) {
	/* XXX this API needs to be changed... what if this descriptor is closed? */
	if ((fp = fopen("/dev/null", "w")) == NULL)
	  return(-1);
  }

  /*
   * POSIX says that vfprintf() is equivalent to fprintf(), except for the
   * stdarg argument list, and that fprintf() "shall return the number of
   * bytes transmitted" unless an output error is encountered, in which case
   * a negative value is returned.
   */
  if ((st = vfprintf(fp, fmt, ap)) < 0)
	return(-1);

  *flen = (size_t) st;
  return(0);
}

static char *
check_entrance(Ds *ds, unsigned int offset)
{

  ds_errmsg = NULL;

  if (ds == NULL)
	ds_errmsg = "internal error: ds == NULL";
  else if (ds->buf == NULL) {
	if (ds->nalloc != 0 || offset != 0)
	  ds_errmsg = "buf == NULL";
  }
  else {
	if (offset > (unsigned int) ds->len || ds->len > ds->nalloc)
	  ds_errmsg = "buf != NULL";
  }

  return(ds_errmsg);
}

static char *
check_exit(Ds *ds)
{

  ds_errmsg = NULL;

  if (ds->len > ds->nalloc)
	ds_errmsg = "invalid len";

  return(ds_errmsg);
}

static void *
alloc_buf(Ds *ds, size_t nbytes)
{

  if ((ds->buf = ds->malloc(nbytes)) == NULL) {
	ds_errmsg = "malloc failed";
	ds->nalloc = 0;
  }
  else
	ds->nalloc = nbytes;

  return(ds->buf);
}

static void
free_buf(Ds *ds)
{

  if (ds->buf != NULL) {
	if (ds->clear_flag)
	  memzap(ds->buf, ds->nalloc);
	ds->free(ds->buf);
	ds->buf = NULL;
	ds->nalloc = 0;
	/* Let the caller adjust ds->len if necessary */
  }
}

static void *
save_buf(Ds *ds)
{
  void *saved;

  if ((saved = ds->buf) != NULL) {
	ds->buf = NULL;
	ds->nalloc = 0;
	ds->len = 0;
  }

  return(saved);
}

static int
grow(Ds *ds, size_t new_size)
{
  void *nbuf;

  ds_errmsg = NULL;
  if ((nbuf = ds->malloc(new_size)) == NULL) {
	ds_errmsg = "malloc failed";
	return(-1);
  }

  memmove(nbuf, ds->buf, ds->len);
  if (ds->clear_flag)
	memzap(ds->buf, ds->len);
  ds->free(ds->buf);
  ds->buf = nbuf;
  ds->nalloc = new_size;

  return(0);
}

typedef enum {
  DS_INSERT_MODE = 0,
  DS_APPEND_MODE = 1
} Ds_mode;

/*
 * Make the buffer large enough so that LEN bytes (LEN != 0) can be copied
 * (DS_APPEND_MODE) or inserted (DS_INSERT_MODE) into it
 * starting at byte OFFSET.
 * Return the number of bytes that will be in the buffer after that copy
 * occurs or 0 on error.
 */
static size_t
prepare_buf(Ds *ds, size_t len, unsigned int offset, Ds_mode mode)
{
  char *msg;
  size_t need, nlen, olen;

  ds_errmsg = NULL;
  if (len == 0) {
	msg = "prepare_buf: len == 0";
	goto fail;
  }

  if ((msg = check_entrance(ds, offset)) != NULL)
	goto fail;

  if (mode == DS_INSERT_MODE) {
	nlen = ds->len + len;
	olen = 0;
  }
  else {
	olen = (ds->buf == NULL) ? 0 : (ds->len - offset);
	nlen = (ds->len - olen) + len;
	if (nlen < ds->len)
	  nlen = ds->len;
  }

  if (ds->len_limit > 0 && nlen > ds->len_limit) {
	msg = "max length exceeded";
	goto fail;
  }

  if (ds->buf == NULL) {
	if ((ds->buf = ds->malloc(len)) == NULL) {
	  msg = "malloc failed";
	  goto fail;
	}
	ds->nalloc = len;
  }
  else {
	int check_len;

	check_len = (int) len + (int) ds->len - (int) olen;
	if (check_len < 0) {
	  msg = "overflow";
	  goto fail;
	}
	need = (size_t) check_len;

	if (need > ds->nalloc) {
	  /*
	   * We need to grow the allocation.
	   * We either allocate the exact amount we need or double that amount.
	   */
	  need = len + ds->len - olen;
	  if (!ds->exact_flag)
		need *= 2;

	  if (grow(ds, need) == -1) {
		msg = ds_errmsg;
		goto fail;
	  }
	}
  }

  return(nlen);

 fail:
  ds_errmsg = msg;
  return(0);
}

static int
ds_doprnt(Ds *ds, unsigned int offset, Ds_mode mode, const char *fmt,
		  va_list ap)
{
  int n;
  char save_ch;
  size_t flen, need, nlen;
  char *msg;
  va_list ap2;

  ds_errmsg = NULL;
  if ((msg = check_entrance(ds, offset)) != NULL)
	goto fail;
  if (ds->buf != NULL && ds->len > ds->nalloc) {
	msg = "len>nalloc";
	goto fail;
  }

  /*
   * The argument list is traversed twice; once when determining the
   * string length and again when producing the real string.
   * On some platforms we need to use separate va_lists.
   */
  va_copy(ap2, ap);
  if (get_formatted_length(fmt, ap2, &flen) == -1) {
	va_end(ap2);
	msg = "get_formatted_length failed";
	goto fail;
  }
  va_end(ap2);

  if (mode == DS_INSERT_MODE)
	need = flen;
  else
	need = flen + 1;	/* Count the null, only if appending. */

  if ((int) (nlen = prepare_buf(ds, need, offset, mode)) == -1)
	return(-1);

  if (mode == DS_INSERT_MODE) {
	/* Save the character that will be overwritten with a null byte. */
	save_ch = *((char *) ds->buf + offset);
	memmove((char *) ds->buf + flen + offset, (char *) ds->buf + offset,
			ds->len);
  }

  if ((n = vsprintf((char *) ds->buf + offset, fmt, ap)) < 0) {
	msg = "vsprintf failed";
	goto fail;
  }
  if ((size_t) n != flen) {
	/* There is a problem - we didn't format the expected number of bytes. */
	msg = "character count is wrong!";
	goto fail;
  }

  if (mode == DS_INSERT_MODE)
	*((char *) ds->buf + flen + offset) = save_ch;

  /* If the length has shrunk, we may want to zap the tail. */
  if (ds->clear_flag && nlen < ds->len)
	memzap((char *) ds->buf + offset + n, (size_t) (ds->len - nlen));

  ds->len = nlen;

  if ((msg = check_exit(ds)) != NULL)
	goto fail;

  if (ds->exact_flag) {
	if (ds_trim(ds) == NULL) {
	  msg = "ds_trim() failed";
	  goto fail;
	}
  }

  return(n);

 fail:
  ds_errmsg = msg;
  return(-1);
}

int ds_default_clear_flag = 0;
int ds_default_delnl_flag = 0;
int ds_default_crnl_flag = 0;
int ds_default_escnl_flag = 0;
size_t ds_default_len_limit = 0;
void *(*ds_default_malloc_func)(size_t size) = malloc;
void (*ds_default_free_func)(void *ptr) = free;

/*
 * Initialize DS to default values, with no buffer allocation.
 */
static void
set_default_ds(Ds *ds)
{

  ds->clear_flag = ds_default_clear_flag;
  ds->exact_flag = 0;
  ds->delnl_flag = ds_default_delnl_flag;
  ds->crnl_flag = ds_default_crnl_flag;
  ds->escnl_flag = ds_default_escnl_flag;
  ds->buf = NULL;
  ds->nalloc = 0;
  ds->len = 0;
  ds->len_limit = ds_default_len_limit;
  ds->malloc = ds_default_malloc_func;
  ds->free = ds_default_free_func;
  ds->io = NULL;
}

/*
 * Allocate a new dynamic string structure and initialize it to defaults.
 */
static Ds *
new_ds(void)
{
  Ds *ds;

  if ((ds = ALLOC(Ds)) == NULL) {
	ds_errmsg = "malloc failed";
	return(NULL);
  }

  ds->alloc_flag = 1;
  set_default_ds(ds);

  return(ds);
}

/*
 * Initialize a dynamic string without allocating any space.
 */
Ds *
ds_init(Ds *ods)
{
  Ds *ds;

  ds_errmsg = NULL;
  if (ods == NULL) {
	if ((ds = new_ds()) == NULL)
	  return(NULL);
  }
  else {
	ds = ods;
	ds->alloc_flag = 0;
	set_default_ds(ds);
  }

  return(ds);
}

/*
 * Initialize a dynamic string and allocate NBYTES of space.
 */
Ds *
ds_init_size(Ds *ods, size_t nbytes)
{
  Ds *ds;

  ds_errmsg = NULL;
  if (ods == NULL) {
	if ((ds = new_ds()) == NULL)
	  return(NULL);
  }
  else {
	ds = ods;
	ds->alloc_flag = 0;
	set_default_ds(ds);
  }

  if ((ds->buf = ds->malloc(nbytes)) == NULL) {
	ds_errmsg = "malloc failed";
	return(NULL);
  }
  ds->nalloc = nbytes;

  return(ds);
}

/*
 * Allocate and initialize a new dynamic string.
 * This is just a variation of the ds_init() API.
 */
Ds *
ds_alloc(void)
{
  Ds *ds;

  ds_errmsg = NULL;
  if ((ds = new_ds()) == NULL)
	return(NULL);

  return(ds);
}

/*
 * Allocate and initialize a new dynamic string, giving it an allocation
 * of NBYTES.
 * This is just a variation of the ds_init_size() API.
 */
Ds *
ds_alloc_size(size_t nbytes)
{
  Ds *ds;

  ds_errmsg = NULL;
  if ((ds = new_ds()) == NULL)
	return(NULL);

  if ((ds->buf = ds->malloc(nbytes)) == NULL) {
	ds_errmsg = "malloc failed";
	free(ds);
	return(NULL);
  }
  ds->nalloc = nbytes;

  return(ds);
}

/*
 * Reinitialize (or create) a dynamic string to "empty", freeing any
 * existing allocation.
 */
Ds *
ds_reinit(Ds *ds)
{

  if (ds == NULL)
	return(ds_init(NULL));

  free_buf(ds);

  set_default_ds(ds);

  return(ds);
}

/*
 * Reset (or create) a dynamic string to "empty", freeing any existing
 * allocation, and creating a new allocation of NBYTES.
 */
Ds *
ds_reinit_size(Ds *ods, size_t nbytes)
{
  Ds *ds;

  if ((ds = ds_reinit(ods)) == NULL)
	return(NULL);

  if ((ds->buf = ods->malloc(nbytes)) == NULL) {
	ds_errmsg = "malloc failed";
	return(NULL);
  }
  ds->nalloc = nbytes;

  return(ds);
}

/*
 * Ensure that DS has at least NBYTES more available and then mark those NBYTES
 * as being used.  Return a pointer to the first of those bytes; note that if
 * the buffer is not empty upon entry, the "fill" is therefore appended to
 * the existing buffer contents.
 *
 * This unusual interface is useful when data needs to be copied into
 * a dynamic string by some "foreign" function rather than by a function in this
 * library, allowing us to hide the internal workings.
 *
 * Example:
 *    ds = ds_init(NULL);
 *    memset(ds_fill(ds, 64), 17, 64);
 *    p1 = ds_buf(ds);
 *    memset(ds_fill(ds, 64), 19, 64);
 *    p2 = ds_buf(ds);
 * This sets the first 64 bytes to be 17 and the next 64 bytes to 19.
 * Note that p1 may not equal p2; if p1 must not change, then ds should be
 * created with a sufficient allocation size.
 */
char *
ds_fill(Ds *ds, size_t nbytes)
{
  char *startp;
  size_t need;

  ds_errmsg = NULL;
  need = ds->len + nbytes;
  if (ds->nalloc < need) {
	if (grow(ds, need) == -1)
	  return(NULL);
  }

  startp = (char *) ds->buf + ds->len;
  ds->len += nbytes;

  return(startp);
}

/*
 * Clear the contents of the buffer without changing the allocation,
 * or create a new one.
 */
Ds *
ds_reset(Ds *ds)
{

  if (ds == NULL)
	return(ds_init(NULL));

  if (ds->buf != NULL && ds->clear_flag)
	memzap(ds->buf, ds->len);

  ds->len = 0;

  return(ds);
}

/*
 * Reinitialize the structure so that subsequent allocations go to a new
 * memory allocation, or create a new dynamic string.  This is used to prevent
 * having to unnecessarily copy the contents of a buffer.
 */
Ds *
ds_reset_buf(Ds *ds)
{

  if (ds == NULL)
	return(ds_init(NULL));

  ds->buf = NULL;
  ds->nalloc = 0;
  ds->len = 0;

  return(ds);
}

/*
 * Change the size of DS's buffer allocation to the actual amount needed.
 */
Ds *
ds_trim(Ds *ds)
{

  ds_errmsg = NULL;
  if (ds->nalloc > (ds->len + 1)) {
	void *nbuf;

	if ((nbuf = ds->malloc(ds->len + 1)) == NULL) {
	  ds_errmsg = "malloc failed";
	  return(NULL);
	}
	memmove(nbuf, ds->buf, ds->len + 1);
	if (ds->clear_flag)
	  memzap(ds->buf, (size_t) ds->len + 1);
	ds->free(ds->buf);
	ds->buf = nbuf;
	ds->nalloc = ds->len + 1;
  }

  return(ds);
}

/*
 * Increase the size of an existing buffer to NEW_SIZE, which must be
 * greater than its current size.
 */
Ds *
ds_grow(Ds *ds, size_t new_size)
{

  ds_errmsg = NULL;
  if (ds->nalloc >= new_size) {
	ds_errmsg = "invalid new_size for ds_grow()";
	return(NULL);
  }

  if (grow(ds, new_size) == -1)
	return(NULL);

  return(ds);
}

/*
 * Free dynamic string DS's allocation, if necessary.
 * If DS was itself dynamically allocated, it is also freed.
 */
void
ds_free(Ds *ds)
{

  if (ds != NULL) {
	free_buf(ds);

	dsio_free(ds);

	if (ds->alloc_flag)
	  free(ds);
  }
}

/*
 * Append CH, an arbitrary byte.
 * The resulting string is NOT null-terminated unless a null is explicitly
 * added.  This may be used with a binary string; a null has no special
 * meaning.  Return 0 if successful and -1 on error.
 */
int
ds_appendc(Ds *ds, int ch)
{
  size_t need;
  char *msg, *p;

  ds_errmsg = NULL;
  if ((msg = check_entrance(ds, 0)) != NULL)
	goto fail;

  if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	msg = "max len exceeded";
	goto fail;
  }

  if (ds->buf == NULL) {
	if (ds->exact_flag)
	  need = 1;
	else {
	  /* Set to an arbitrary initial size, but not too big. */
	  need = 16;
	}
	if ((ds->buf = ds->malloc(need)) == NULL) {
	  msg = "malloc failed";
	  goto fail;
	}
	ds->nalloc = need;
	p = (char *) ds->buf;
  }
  else {
	if (ds->len == ds->nalloc) {
	  /* Grow the allocation. */
	  if (ds->exact_flag)
		need = ds->nalloc + 1;
	  else
		need = ds->nalloc * 2;

	  if (grow(ds, need) == -1)
		goto fail;
	}
	p = (char *) ds->buf + ds->len;
  }

  *p = ch;

  ds->len++;
  if (ds->len == 0) {
	msg = "overflow3";
	goto fail;
  }

  if ((msg = check_exit(ds)) != NULL)
	goto fail;

  return(0);

 fail:
  ds_errmsg = msg;
  return(-1);
}

/*
 * Copy (overwrite) LEN bytes pointed to by SRC into the buffer, starting
 * at OFFSET bytes within the buffer (0 <= OFFSET <= ds->len).
 * The source can be arbitrary bytes.
 * Return a pointer to the start of the inserted bytes or NULL on error.
 */
char *
ds_copyb(Ds *ds, void *src, size_t len, unsigned int offset)
{
  size_t nlen, olen;

  ds_errmsg = NULL;

  if (len == 0)
	return((char *) ds->buf + offset);

  olen = ds->len;
  if ((int) (nlen = prepare_buf(ds, len, offset, DS_APPEND_MODE)) == -1)
	return(NULL);

  memmove((char *) ds->buf + offset, src, len);

  /* If the length has shrunk, we may want to zap the tail. */
  if (ds->clear_flag && nlen < ds->len)
	memzap((char *) ds->buf + offset + len, (size_t) (ds->len - nlen));

  ds->len = nlen;

  if ((ds_errmsg = check_exit(ds)) != NULL)
	return(NULL);

  return((char *) ds->buf + offset);
}

/*
 * Insert (without overwriting) LEN bytes pointed to by SRC into the buffer,
 * starting at OFFSET bytes within the buffer (0 <= OFFSET <= ds->len).
 * The source can be arbitrary bytes.
 * Return a pointer to the start of the inserted bytes or NULL on error.
 */
char *
ds_insertb(Ds *ds, void *src, size_t len, unsigned int offset)
{
  int nlen;

  ds_errmsg = NULL;

  if (len == 0)
	return((char *) ds->buf + offset);

  if ((nlen = prepare_buf(ds, len, offset, DS_INSERT_MODE)) == -1)
	return(NULL);

  memmove((char *) ds->buf + offset + len,
		  (char *) ds->buf + offset,
		  ds->len - offset);
  memmove((char *) ds->buf + offset, src, len);

  ds->len = nlen;

  if ((ds_errmsg = check_exit(ds)) != NULL)
	return(NULL);

  return((char *) ds->buf + offset);
}

char *
ds_prepend(Ds *ds, char *str)
{
  size_t len;

  if (ds_len(ds) == 0)
	ds_appendc(ds, (int) '\0');

  len = strlen(str);
  ds_insertb(ds, str, len, 0);

  return(ds_buf(ds));
}

/*
 * Prepend CH, an arbitrary byte.
 * The resulting string is NOT null-terminated unless a null is explicitly
 * added.  This may be used with a binary string; a null has no special meaning.
 * Return 0 if successful and -1 on error.
 */
int
ds_prependc(Ds *ds, int ch)
{
  int st;
  size_t len;

  if (ds_len(ds) == 0)
	st = (ds_appendc(ds, ch) == 0 ? 0 : -1);
  else {
	char c;

	c = (char) ch;
	st = (ds_insertb(ds, &c, 1, 0) == NULL ? -1 : 0);
  }

  return(st);
}

/*
 * Append string STR to the buffer.
 * The resulting string is NOT null-terminated.
 * Return a pointer to the start of the copied string or NULL on error.
 */
char *
ds_append(Ds *ds, char *str)
{
  size_t len;

  len = strlen(str);
  return(ds_copyb(ds, str, len, ds->len));
}

/*
 * Append string STR of length LEN bytes to the buffer.
 * The resulting string is NOT null-terminated.
 * Return a pointer to the start of the copied string or NULL on error.
 */
char *
ds_appendn(Ds *ds, char *str, size_t len)
{

  return(ds_copyb(ds, str, len, ds->len));
}

/*
 * Append N (N > 0) buffers to an existing buffer (DSX non-NULL) or to
 * create a new buffer (DSX is NULL).
 * If NO_NULL is non-zero, then DSX (if it exists) and each append buffer
 * is assumed to be null-terminated and those null characters are not copied.
 * Regardless of NO_NULL, the resulting string is not null-terminated.
 */
Ds *
ds_dsvaappend(Ds *dsx, int no_null, unsigned int n, va_list ap)
{
  int i;
  size_t len;
  Ds *arg, *ds;

  if (n == 0)
	return(dsx);

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else {
	ds = dsx;
	/*
	 * If we're eliding null characters, zap the one at the end of the
	 * buffer to which we're appending.
	 */
	if (no_null && ds_len(ds) > 0)
	  ds->len--;
  }

  for (i = 0; i < n; i++) {
	arg = va_arg(ap, Ds *);
	if (ds_len(arg) > 0) {
	  len = ds_len(arg) - (no_null != 0);
	  ds_appendn(ds, ds_buf(arg), len);
	}
  }

  return(ds);
}

/*
 * Append N (N > 0) buffers to an existing buffer (DSX non-NULL) or to
 * create a new buffer (DSX is NULL).
 * If NO_NULL is non-zero, then DSX (if it exists) and each append buffer
 * is assumed to be null-terminated and those null characters are not copied.
 * Regardless of NO_NULL, the resulting string is *not* null-terminated.
 */
Ds *
ds_dsappend(Ds *dsx, int no_null, unsigned int n, ...)
{
#ifdef NOTDEF
  int i;
  size_t len;
  Ds *arg, *ds;
  va_list ap;

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else
	ds = dsx;

  va_start(ap, n);
  for (i = 0; i < n; i++) {
	arg = va_arg(ap, Ds *);
	if (ds_len(arg) > 0) {
	  len = ds_len(arg) - (no_null != 0);
	  ds_appendn(ds, ds_buf(arg), len);
	}
  }
  va_end(ap);

  return(ds);
#else
  Ds *ds;
  va_list ap;

  va_start(ap, n);
  ds = ds_dsvaappend(dsx, no_null, n, ap);
  va_end(ap);

  return(ds);
#endif
}

#ifdef NOTDEF
/*
 * Concatenate SECONDARY to the string at the end of PRIMARY.
 * The resulting string is null-terminated.
 * Return a pointer to the start of the copied string or NULL on error.
 */
char *
ds_concatb(Ds *primary, Ds *secondary)
{
  size_t len;

  len = secondary->len;
  if (primary->len == 0)
	return(ds_copyb(primary, secondary->buf, len, 0));
  return(ds_copyb(primary, secondary->buf, len, primary->len - 1));
}
#endif

/*
 * Concatenate string STR to the string at the end of the buffer.
 * The resulting string is null-terminated.
 * Return a pointer to the start of the copied string or NULL on error.
 */
char *
ds_concat(Ds *ds, char *str)
{
  size_t len;

  len = strlen(str) + 1;
  if (ds->len == 0)
	return(ds_copyb(ds, str, len, 0));
  return(ds_copyb(ds, str, len, ds->len - 1));
}

/*
 * Concatenate byte CH to the string at the end of the buffer.
 * The resulting string is null-terminated.
 * Return a pointer to the position of the copied character or NULL on error.
 */
char *
ds_concatc(Ds *ds, int ch)
{
  char c, *ptr;

  c = ch;
  if (ds->len == 0)
	ptr = ds_copyb(ds, &c, 1, 0);
  else
	ptr = ds_copyb(ds, &c, 1, ds->len - 1);

  c = '\0';
  ds_copyb(ds, &c, 1, ds->len);
  return(ptr);
}

/*
 * Concatenate LEN bytes from STR and null-terminate the resulting string.
 * Return a pointer to the start of the copied string or NULL on error.
 */
char *
ds_concatn(Ds *ds, char *str, size_t len)
{
  char *s;

  if (ds->len == 0)
	s = ds_copyb(ds, str, len, 0);
  else
	s = ds_copyb(ds, str, len, ds->len - 1);
  if (s != NULL) {
	if (ds_appendc(ds, (int) '\0') == -1)
	  return(NULL);
	s = ds_buf(ds);
  }

  return(s);
}

/*
 * Set the buffer to STR and return the buffer or NULL on error.
 */
Ds *
ds_set(Ds *dsx, char *str)
{
  Ds *ds;
  size_t len;

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else {
	ds = dsx;
	ds_reset(ds);
  }

  if (str == NULL)
	return(ds);

  len = strlen(str) + 1;
  if (ds_copyb(ds, str, len, 0) == NULL)
	return(NULL);

  return(ds);
}

/*
 * Set the buffer to the first SLEN bytes of S.
 * Note that the resulting buffer need not be null-terminated.
 * Arbitrary bytes can be copied.
 */
Ds *
ds_setn(Ds *dsx, unsigned char *s, size_t slen)
{
  Ds *ds;

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else {
	ds = dsx;
	ds_reset(ds);
  }

  if (ds_copyb(ds, s, slen, 0) == NULL)
	return(NULL);

  return(ds);
}

/*
 * Make a copy of the argument and its current buffer contents.
 */
Ds *
ds_dup(Ds *dsx)
{
  Ds *ds;

  if (dsx == NULL)
	return(NULL);

  ds = ds_init_size(NULL, dsx->nalloc);
  ds->alloc_flag = dsx->alloc_flag;
  ds->clear_flag = dsx->clear_flag;
  ds->exact_flag = dsx->exact_flag;
  ds->delnl_flag = dsx->delnl_flag;
  ds->crnl_flag = dsx->crnl_flag;
  ds->escnl_flag = dsx->escnl_flag;
  ds->len = dsx->len;
  ds->len_limit = dsx->len_limit;
  ds->malloc = dsx->malloc;
  ds->free = dsx->free;
  ds->io = dsx->io;

  memcpy(ds->buf, dsx->buf, dsx->len);

  return(ds);
}

/*
 * Delete a null byte at the end of DS, if it exists.
 * This is done to simplifying concatenating additional material to DS.
 * Return 1 if zapping was done, 0 otherwise.
 */
int
ds_zapnull(Ds *ds)
{
  char *p;

  if (ds == NULL || ds->len == 0)
	return(0);

  p = (char *) ds->buf;
  p += ds->len - 1;
  if (*p == '\0') {
	ds->len--;
	return(1);
  }

  return(0);
}

/*
 * Append a span of zero or more characters from S until either a character in
 * END_CHARS is seen or the end of S is found (the terminating character
 * is not copied).  The resulting buffer is always null terminated.
 */
Ds *
ds_copyspn(Ds *dsx, unsigned char *s, char *end_chars)
{
  unsigned char *p;
  Ds *ds;

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else
	ds = dsx;

  /* This could use strcspn() and ds_copyb() instead. */
  for (p = s; *p != '\0'; p++) {
	if (strchr(end_chars, (int) *p) != NULL)
	  break;
	ds_appendc(ds, *p);
  }

  ds_appendc(ds, (int) '\0');

  return(ds);
}

/*
 * Create a new string by selecting characters from STR according to
 * RANGE_SPEC.
 */
Ds *
ds_range(Ds *dsx, char *str, char *range_spec, Range_syntax *ors)
{
  int i, rc;
  size_t len;
  Ds *ds;
  Range_syntax *rs;

  ds_errmsg = NULL;
  if (str == NULL)
	return(NULL);

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else {
	ds_reset(dsx);
	ds = dsx;
  }

  if (ors == NULL) {
    rs = range_set_default_syntax(NULL);
    rs->span_sep_str = "-";
    rs->signed_values = 0;
  }
  else
    rs = ors;

  len = strlen(str);
  for (i = 0; i < len; i++) {
	if ((rc = range_test(i, range_spec, rs, &ds_errmsg)) == -1)
	  return(NULL);
	else if (rc == 1)
	  ds_appendc(ds, str[i]);
  }

  ds_appendc(ds, '\0');

  return(ds);
}

/*
 * Substitute (destructively) each occurrence of CH_SRC in DS with CH_RPL.
 */
Ds *
ds_subst(Ds *ds, unsigned char ch_src, unsigned char ch_rpl)
{
  int i;
  unsigned char *p;

  p = (unsigned char *) ds_buf(ds);
  for (i = 0; i < ds_len(ds); i++) {
	if (*p == ch_src)
	  *p = ch_rpl;
	p++;
  }

  return(ds);
}

/*
 * Implicitly remove CHOP bytes from the end of DS and return the new length.
 * Do nothing if CHOP is greater than the current length of DS.
 * Note that the resulting buffer may not be null-terminated.
 */
size_t
ds_chop(Ds *ds, size_t chop)
{
  size_t new_len;

  if (chop > ds->len)
	return(ds->len);

  ds->len -= chop;

  return(ds->len);
}

/*
 * Read an arbitrary byte from FP and append it to the buffer, if not
 * EOF or error.  Return the character using CH_PTR, if non-NULL.
 * Return 1 if successful, 0 if EOF is reached, -1 if a read error occurred,
 * and -2 if the buffer limit was reached.
 */
int
ds_getc(Ds *ds, FILE *fp, int *ch_ptr)
{
  int ch;

  ds_errmsg = NULL;
  if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	ds_errmsg = "max limit exceeded";
	return(-2);
  }

  if ((ch = getc(fp)) == EOF) {
	if (feof(fp))
	  return(0);
	if (ferror(fp))
	  return(-1);
  }

  if (ds_appendc(ds, ch) == -1)
	return(-1);

  if (ch_ptr != NULL)
	*ch_ptr = ch;

  return(1);
}

/*
 * Set the input source to either:
 *   a stream (if FP is non-NULL, up to LEN bytes if HAVE_LENGTH is non-zero),
 *   a buffer of length LEN (if FP is NULL and HAVE_LENGTH is non-zero),
 *   a null-terminated string of unknown length (if FP is NULL and
 *   HAVE_LENGTH is zero).
 * Note that in the buffer/string cases, the buffer/string is not copied.
 *
 * XXX maybe should support reading a descriptor as an alternative
 * XXX should support non-blocking I/O on FP by blocking or polling
 * XXX Could support user-supplied eof/nextc/peeks functions
 */
Dsio *
dsio_set(Ds *ds, FILE *fp, char *buf, unsigned long len, int have_length)
{
  Dsio *in;

  if ((fp == NULL && buf == NULL) || (fp != NULL && buf != NULL))
    return(NULL);

  if (ds->io == NULL)
	in = ds->io = ALLOC(Dsio);
  else
	in = ds->io;

  in->fp = fp;
  in->buf = in->ptr = buf;

  if (buf != NULL && have_length == 0) {
	in->length = in->rem = strlen(buf);
	in->have_length = 1;
  }
  else {
	in->length = in->rem = len;
	in->have_length = have_length;
  }

  in->got_peeked_char = 0;
  in->peeked_char = 0;

  return(in);
}

/*
 * Note that if there's an input stream it is not closed, nor is any input
 * buffer freed.
 */
int
dsio_free(Ds *ds)
{

  if (ds->io != NULL)
	free(ds->io);

  return(0);
}

int
dsio_eof(Ds *ds)
{

  if (ds->io->got_peeked_char)
    return(0);

  if (ds->io->fp != NULL) {
    if (ferror(ds->io->fp))
      return(-1);
    return(feof(ds->io->fp) || (ds->io->have_length && ds->io->rem == 0));
  }

  return(ds->io->ptr == NULL
		 || (ds->io->have_length && ds->io->rem == 0)
		 || *ds->io->ptr == '\0');
}

/*
 * Return 1 and set CH to the next arbitrary input byte, return 0 on EOF,
 * and return -1 on error.
 */
int
dsio_nextc(Ds *ds, int *ch_ptr)
{
  char ch;
  int rc;
  
  if ((rc = dsio_eof(ds)) == -1)
    return(-1);

  if (rc == 1)
    return(0);

  rc = 1;
  if (ds->io->got_peeked_char) {
    ch = ds->io->peeked_char & 0377;
    ds->io->got_peeked_char = 0;
  }
  else {
    if (ds->io->fp != NULL) {
      if (fread(&ch, 1, 1, ds->io->fp) != 1) {
        rc = -1;
        if (feof(ds->io->fp))
          rc = 0;
      }
    }
    else
      ch = (int) *ds->io->ptr++;
  }

  if (rc == 1) {
	if (ds->io->have_length)
	  ds->io->rem--;
	*ch_ptr = (int) ch;
  }

  return(rc);
}

/*
 * There is a one byte lookahead buffer.
 * Return 1 and set CH if that character is available, return 0 if EOF has
 * been reached, and return -1 on error.
 */
int
dsio_peekc(Ds *ds, int *ch_ptr)
{
  int rc;

  if (ds->io->got_peeked_char)
    return(-1);

  if ((rc = dsio_eof(ds)) == -1)
    return(-1);

  if (rc == 1)
    return(0);

  rc = 1;
  if (ds->io->fp != NULL) {
    if (fread(&ds->io->peeked_char, 1, 1, ds->io->fp) != 1) {
      rc = -1;
      if (feof(ds->io->fp))
        rc = 0;
    }
  }
  else
    ds->io->peeked_char = *ds->io->ptr++;

  if (rc == 1) {
    ds->io->got_peeked_char = 1;
    *ch_ptr = (int) ds->io->peeked_char;
  }

  return(rc);
}

/*
 * Multi-source input version of ds_agets().
 */
char *
dsio_agets(Ds *ds)
{
  int backup, ch, saw_ch, saw_esc;
  char *p;
  size_t olen;

  ds_errmsg = NULL;
  if (ds->io == NULL) {
	ds_errmsg = "no input source is configured";
	return(NULL);
  }

  if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	ds_errmsg = "max limit exceeded";
	return(NULL);
  }

  if ((olen = ds->len) != 0)
	backup = 1;
  else
	backup = 0;

  saw_ch = 0;
  saw_esc = 0;
  while (dsio_nextc(ds, &ch) == 1) {
	if (ds->escnl_flag) {
	  /*
	   * Replace an escaped newline with a single space.
	   * XXX Does not work with crnl_flag, which would involve
	   * replacing backslash-cr-nl with a space.
	   */
	  if (saw_esc) {
		saw_esc = 0;
		if (ch == '\n') {
		  p = (char *) ds->buf;
		  p[ds->len - 1] = ' ';
		  continue;
		}
	  }
	  else if (ch == '\\')
		saw_esc = 1;
	}

	/*
	 * A character was read.
	 * We're appending so overwrite a null if it's there.
	 */
	saw_ch = 1;
	p = (char *) ds->buf;
	if (backup && p[ds->len - 1] == '\0') {
	  ds->len--;
	  olen--;
	  backup = 0;
	}

	if (ch == '\n') {
	  /* If we're accepting \r\n, delete the \r if necessary. */
	  if (ds->crnl_flag && ds->len != 0 && p[ds->len - 1] == '\r')
		ds->len--;

	  if (!ds->delnl_flag) {
		if (ds_appendc(ds, ch) == -1)
		  return(NULL);
	  }
	  break;
	}

	if (ds_appendc(ds, ch) == -1)
	  return(NULL);

	if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	  ds_errmsg = "max limit exceeded";
	  return(NULL);
	}
  }

  if (dsio_eof(ds) == -1)
	return(NULL);

  if (!saw_ch && olen == ds->len)
	return(NULL);

  /* Something was read, null-terminate the string. */
  if (ds_appendc(ds, (int) '\0') == -1)
	return(NULL);

  return((char *) ds->buf + olen);
}

char *
dsio_gets(Ds *ds)
{

  return(dsio_agets(ds_reset(ds)));
}

/*
 * Read arbitrary bytes from the current dsio location until EOF, error,
 * or a limit.
 * The resulting buffer is NOT null-terminated.
 * This is intended to be a more efficient alternative to dsio_nextc() for
 * bulk data.
 * Return 1 if ok, 0 if EOF, and -1 if an error occurs.
 */
int
dsio_load(Ds *ds)
{
  int ch, rc;

  ds_errmsg = NULL;
  if (ds == NULL || ds->io == NULL) {
	ds_errmsg = "no input source is configured";
	return(-1);
  }

  if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	ds_errmsg = "max limit exceeded";
	return(-1);
  }

  if ((rc = dsio_eof(ds)) == -1)
    return(-1);

  if (rc == 1)
    return(0);

  rc = 1;
  if (ds->io->got_peeked_char) {
    ch = ds->io->peeked_char & 0377;
	if (ds_appendc(ds, ch) == -1)
	  return(-1);
	ds->io->got_peeked_char = 0;
  }

  if (ds->io->fp != NULL) {
	while (dsio_eof(ds) == 0) {
	  char ach;

	  if (fread(&ach, 1, 1, ds->io->fp) != 1)
		break;
	  if (ds_appendc(ds, (int) ach) == -1)
		return(-1);
	  if (ds->io->have_length)
		ds->io->rem--;
	}
	if (ferror(ds->io->fp))
	  return(-1);
  }
  else {
	if (ds->io->length > ds->io->rem)
	  return(-1);
	if (ds_copyb(ds, ds->io->ptr, ds->io->length, ds->len) == NULL)
	  return(-1);
	ds->io->ptr += ds->io->length;
	ds->io->rem -= ds->io->length;
  }

  return(1);
}

/*
 * Read a string from the current dsio location until EOF or error.
 * Perform crnl-to-nl mapping if requested.
 * Return the string or NULL.
 */
char *
dsio_load_str(Ds *ds)
{
  int backup, ch, saw_ch;
  char *p;
  size_t olen;

  ds_errmsg = NULL;
  if (ds == NULL || ds->io == NULL) {
	ds_errmsg = "no input source is configured";
	return(NULL);
  }

  if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	ds_errmsg = "max limit exceeded";
	return(NULL);
  }

  if ((olen = ds->len) != 0)
	backup = 1;
  else
	backup = 0;

  saw_ch = 0;
  while (dsio_nextc(ds, &ch) == 1) {
	/*
	 * A byte was read.
	 * We're appending so overwrite a null if it's there.
	 */
	saw_ch = 1;
	p = (char *) ds->buf;
	if (backup && p[ds->len - 1] == '\0') {
	  ds->len--;
	  olen--;
	  backup = 0;
	}

	if (ch == '\n') {
	  /* If we're accepting \r\n, delete the \r if necessary. */
	  if (ds->crnl_flag && ds->len != 0 && p[ds->len - 1] == '\r')
		ds->len--;
	}

	if (ds_appendc(ds, ch) == -1)
	  return(NULL);

	if (ds->len_limit > 0 && ds->len == ds->len_limit) {
	  ds_errmsg = "max limit exceeded";
	  return(NULL);
	}
  }

  if (dsio_eof(ds) == -1)
	return(NULL);

  if (!saw_ch && olen == ds->len)
	return(NULL);

  /* Something was read, null-terminate the string. */
  if (ds_appendc(ds, (int) '\0') == -1)
	return(NULL);

  return((char *) ds->buf + olen);
}

/*
 * Read a string into the buffer, replacing whatever might already be
 * there.  An input string is terminated by EOF or a newline character
 * (carriage return, newline if crnl_flag is set).
 * The newline character is put in the buffer unless delnl_flag is non-zero.
 * If successful, the buffer will be null-terminated.
 * If an error occurs, NULL is returned, otherwise a pointer to the
 * buffer is returned.
 */
char *
ds_gets(Ds *ds, FILE *fp)
{

  return(ds_agets(ds_reset(ds), fp));
}

/*
 * Read a string into the buffer, appending it to whatever might already be
 * there.  An input string is terminated by EOF or a newline character
 * (carriage return, newline if crnl_flag is set).
 * The newline character is put in the buffer, if it was read and
 * delnl_flag is 0.
 * If successful, the buffer will be null-terminated.
 * If an error occurs, NULL is returned, otherwise a pointer to the
 * start of the string read by the latest call is returned.
 * The caller must use feof() and ferror() to know exactly what happened.
 */
char *
ds_agets(Ds *ds, FILE *fp)
{

  if (ds->io == NULL)
	dsio_set(ds, fp, NULL, 0, 0);

  return(dsio_agets(ds));
}

char *
dsio_agets_buf(Ds *ds, char *buf)
{

  if (ds->io == NULL)
	dsio_set(ds, NULL, buf, strlen(buf), 1);

  return(dsio_agets(ds));
}

#include <pwd.h>
#include <unistd.h>

#ifdef HAVE_TERMIOS_H

#include <termios.h>

#ifndef TCSASOFT
#define TCSASOFT	0
#endif

/*
 * Turn echo off for FP (which is a suitable tty device).
 */
static int
set_echo_off(FILE *fp, struct termios *attr)
{

  attr->c_lflag &= ~(ECHO);
  if (tcsetattr(fileno(fp), TCSAFLUSH | TCSASOFT, attr) == -1)
	return(-1);

  return(0);
}

/*
 * Modify the ATTR, the current tty attributes for FP (a suitable tty device),
 * turning on echo, and then update the device.
 */
static int
set_echo_on(FILE *fp, struct termios *attr)
{

  attr->c_lflag |= ECHO;
  if (tcsetattr(fileno(fp), TCSANOW | TCSASOFT, attr) == -1)
	return(-1);

  return(0);
}

/*
 * Helper for ds_prompt().
 * Note that if the device associated with FP_IN is not a true tty,
 * such as when the typed characters are delivered via a pipe/fifo, the
 * termios functions will fail with EINVAL even though intuitively they
 * should work.
 */
static char *
do_prompt(Ds *ds, FILE *fp_in, FILE *fp_out, char *prompt, unsigned int flag)
{
  int ch, rc;
  struct termios attr;

  memset(&attr, 0, sizeof(attr));
  if (tcgetattr(fileno(fp_in), &attr) == -1)
	return(NULL);

  if (flag == DS_PROMPT_NOECHO) {
	if (set_echo_off(fp_in, &attr) == -1)
	  return(NULL);
  }

  if (prompt != NULL && *prompt != '\0') {
	(void) fputs(prompt, fp_out);
	fflush(fp_out);
  }

  rc = 0;
  while ((ch = fgetc(fp_in)) != EOF && ch != '\n') {
	if (ds_appendc(ds, ch) == -1) {
	  rc = -1;
	  break;
	}
  }
  if (ferror(fp_in))
	rc = -1;

  if (flag == DS_PROMPT_NOECHO) {
	set_echo_on(fp_in, &attr);
	fputc((int) '\n', fp_out);
	fflush(fp_out);
  }

  if (rc != -1) {
	if (ds_appendc(ds, (int) '\0') == -1)
	  return(NULL);
	return(ds_buf(ds));
  }

  return(NULL);
}
#endif

/*
 * A replacement for getpass()/getpassphrase() that allows runtime selection
 * of the maximum input length, returns an indication of whether that limit
 * has been exceeded, and can ensure that the allocated memory is erased
 * when it is freed - but note that the caller must deallocate or explicitly
 * erase the input, if the content is sensitive.
 *
 * The prompt is optional.
 * To put a limit on the size of the input, set ds->len_limit.
 *
 * If /dev/tty is available, PROMPT is written to it and a reply
 * is read from it; if /dev/tty is unavailable, stderr and stdin are
 * used, respectively.
 *
 * If FLAG is DS_PROMPT_ECHO, echo the user input; if it's DS_PROMPT_NOECHO,
 * echo nothing.
 * XXX DS_PROMPT_HIDE would instead echo ds_prompt_hide_char in raw mode
 * instead of whatever's actually typed.
 * XXX DS_PROMPT_MAYECHO would attempt to turn off echo, but continue regardless
 * if that fails.
 *
 * Return the null-terminated user input string or NULL on error.
 * Note that if the input consists of only a newline, an empty string
 * is returned rather than NULL.
 */
char *
ds_prompt(Ds *ds, char *prompt, unsigned int flag)
{
  char *p, *str;
  FILE *fp_out, *fp_in;

#ifndef HAVE_TERMIOS_H
  return(NULL);
#endif

  /* Force the buffer to be erased when freed. */
  ds->clear_flag = 1;

  fp_out = stderr;
  if ((fp_in = fopen("/dev/tty", "w+")) == NULL)
	fp_in = stdin;

  p = do_prompt(ds, fp_in, fp_out, prompt, flag);

  if (fp_in != stdin)
	fclose(fp_in);

  return(p);

  /* This will copy at most ds->len_limit characters and flag an error. */
  str = ds_buf(ds_set(ds, p));

  /* Erase the original input. */
  memzap(p, strlen(p));

  return(str);
}

#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>

/*
 * Read a string (from stdin) into the buffer, replacing whatever might
 * already be there.  An input string is terminated by EOF or a
 * newline character; it can be continued, however, if it ends with a
 * non-escaped backslash.
 *
 * The newline character is appended to the buffer unless delnl_flag is
 * non-zero.
 * If successful, the buffer will be null-terminated.
 * If an error occurs, NULL is returned, otherwise a pointer to the
 * buffer is returned.
 */
char *
ds_readline(Ds *ds, char *prompt, char *cprompt)
{
  int escaped, n;
  char *p, *the_prompt;
  size_t line_len;
  static int done_init = 0;

  if (!done_init) {
	using_history();
	done_init = 1;
  }

  ds_reset(ds);
  /* Force the buffer to be erased when freed. */
  ds->clear_flag = 1;

  the_prompt = prompt;

  n = 0;
  while (1) {
	if (n++ == 1)
	  the_prompt = cprompt;

	if ((p = readline(the_prompt)) == NULL)
	  return(ds_len(ds) == 0 ? NULL : ds_buf(ds));

	escaped = 0;
	line_len = strlen(p);
	if (ds->escnl_flag && line_len > 0 && p[line_len - 1] == '\\') {
	  if (line_len == 1) {
		ds_concat(ds, " ");
		continue;
	  }
	  if (p[line_len - 2] != '\\') {
		/* p[line_len - 1] = ' '; */
		p[line_len - 1] = '\0';
		escaped = 1;
	  }
	  else
		p[line_len - 1] = '\0';
	}

	/* This will copy at most ds->len_limit characters and flag an error. */
	ds_concat(ds, p);
	if (!ds->delnl_flag) {
	  if (ds_appendc(ds, (int) '\n') == -1 || ds_appendc(ds, (int) '\0') == -1)
		return(NULL);
	}

	/* Erase the original input. */
	memzap(p, strlen(p));

	if (!escaped)
	  break;
  }

  if (!streq(ds_buf(ds), ""))
	add_history(ds_buf(ds));

  return(ds_buf(ds));
}
#endif

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

/*
 * Read the entire contents of PATHNAME; if it is NULL, read stdin until EOF.
 * If PATHNAME is a regular file, this is efficient because the contents are
 * slurped up in one read.
 */
Ds *
ds_load_file(Ds *dsx, char *pathname)
{
  int fd;
  char *p;
  struct stat sb;
  ssize_t n;
  Ds *ds;

  if (pathname == NULL)
	return(ds_agetf(dsx, stdin));

  if ((fd = open(pathname, O_RDONLY)) == -1) {
	ds_errmsg = strdup(strerror(errno));				  
	return(NULL);
  }

  if (fstat(fd, &sb) == -1) {
	ds_errmsg = strdup(strerror(errno));
	close(fd);
	return(NULL);
  }

  /*
   * Special handling for special files.
   * We may be told such files have a given size (esp. 0) which is in fact
   * bogus; e.g., files under /proc.
   */
  if (sb.st_size == 0) {
	FILE *fp;

	if ((fp = fdopen(fd, "r")) == NULL) {
	  close(fd);
	  return(NULL);
	}
	ds = ds_agetf(dsx, fp);
	fclose(fp);

	return(ds);
  }

  if (dsx == NULL) {
	if ((ds = ds_init_size(NULL, (size_t) sb.st_size + 1)) == NULL) {
	  close(fd);
	  return(NULL);
	}
  }
  else {
	ds = dsx;
	if (ds->nalloc == 0) {
	  if (alloc_buf(ds, sb.st_size + 1) == NULL) {
		close(fd);
		return(NULL);
	  }
	}
	else if (ds->nalloc < sb.st_size + 1) {
	  free_buf(ds);
	  alloc_buf(ds, (size_t) sb.st_size + 1);
	}
  }

  if (ds->len_limit > 0
	  && ((unsigned int) sb.st_size + 1) >= ds->len_limit) {
	ds_errmsg = "max len exceeded";
	close(fd);
	return(NULL);
  }

  if ((n = read(fd, ds->buf, sb.st_size)) != (ssize_t) sb.st_size) {
	ds_errmsg = strdup(strerror(errno));
	ds_free(ds);
	close(fd);
	return(NULL);
  }
  close(fd);

  p = (char *) ds->buf;
  if (ds->delnl_flag && n > 0 && p[n - 1] == '\n')
	n--;

  p[n] = '\0';
  ds->len = n + 1;

  return(ds);
}

/*
 * Read the contents of stream FP, one line at a time, until EOF.
 */
Ds *
ds_agetf(Ds *dsx, FILE *fp)
{
  Ds *ds;

  if (dsx == NULL) {
	if ((ds = ds_init(NULL)) == NULL)
	  return(NULL);
  }
  else
	ds = dsx;

  if (fp == NULL)
	fp = stdin;

  while (ds_agets(ds, fp) != NULL)
	;

  if (ferror(fp) || !feof(fp)) {
	ds_free(ds);
	return(NULL);
  }

  return(ds);
}

/*
 * Note that the resulting buffer is not trimmed...
 */
Ds *
ds_getf(FILE *fp)
{
  Ds *ds;

  ds = ds_agetf(NULL, fp);

  return(ds);
}

/*
 * Similar to asprintf() this function is a safe implementation of sprintf().
 * The memory to which the formatted string goes is dynamically allocated.
 * Typical usage is something like:
 *   Ds *x = ds_init(NULL);
 *   int n;
 *   n = ds_sprintf(x, 0, "Hello, %s\n", str);
 * after which, n is the length of the formatted string, x->buf points to
 * the formatted string, and x->len is the strlen of x->buf.
 * The OFFSET argument is the offset within the buffer
 * at which the new formatted string should begin.
 * The caller is responsible for freeing x->buf and x.
 *
 * If an error occurs, -1 is returned.
 */
int
ds_sprintf(Ds *ds, unsigned int offset, const char *fmt, ...)
{
  int n;
  va_list ap;

  va_start(ap, fmt);
  n = ds_doprnt(ds, offset, DS_APPEND_MODE, fmt, ap);
  va_end(ap);
  return(n);
}

/*
 * Like ds_sprintf(), but always in "append mode".
 */
int
ds_asprintf(Ds *ds, const char *fmt, ...)
{
  int n;
  char *p;
  va_list ap;

  va_start(ap, fmt);
  p = (char *) ds->buf;
  if (ds->len != 0 && p[ds->len - 1] == '\0')
	n = ds_doprnt(ds, ds->len - 1, DS_APPEND_MODE, fmt, ap);
  else
	n = ds_doprnt(ds, ds->len, DS_APPEND_MODE, fmt, ap);
  va_end(ap);
  return(n);
}

int
ds_vasprintf(Ds *ds, const char *fmt, va_list ap)
{
  int n;
  char *p;

  p = (char *) ds->buf;
  if (ds->len != 0 && p[ds->len - 1] == '\0')
	n = ds_doprnt(ds, ds->len - 1, DS_APPEND_MODE, fmt, ap);
  else
	n = ds_doprnt(ds, ds->len, DS_APPEND_MODE, fmt, ap);
  return(n);
}

/*
 * Like ds_sprintf(), but a "one shot" sprintf().
 */
char *
ds_xprintf(const char *fmt, ...)
{
  Ds ds;
  va_list ap;

  ds_init(&ds);
  ds.exact_flag = 1;

  va_start(ap, fmt);
  if (ds_doprnt(&ds, 0, DS_APPEND_MODE, fmt, ap) == -1) {
	va_end(ap);
	return(NULL);
  }

  va_end(ap);
  return(ds_buf(&ds));
}

char *
ds_vxprintf(const char *fmt, va_list ap)
{
  Ds ds;

  ds_init(&ds);
  ds.exact_flag = 1;

  if (ds_doprnt(&ds, 0, DS_APPEND_MODE, fmt, ap) == -1)
	return(NULL);

  return(ds_buf(&ds));
}

/*
 * Like ds_asprintf() except always in "prepend" mode.
 */
int
ds_psprintf(Ds *ds, const char *fmt, ...)
{
  int n;
  char *p;
  va_list ap;

  va_start(ap, fmt);
  p = (char *) ds->buf;
  if (ds->len == 0)
	ds_appendc(ds, (int) '\0');

  n = ds_doprnt(ds, 0, DS_INSERT_MODE, fmt, ap);
  va_end(ap);
  return(n);
}

typedef struct Ds_stream {
  FILE *fp;
  Ds *ds;
} Ds_stream;

static Dsvec *dsv_streams = NULL;

static void
stream_cleanup(FILE *fp, int do_delete)
{
  unsigned int ui;
  Ds_stream *dss;

  for (ui = 0; ui < dsvec_len(dsv_streams); ui++) {
	dss = dsvec_ptr(dsv_streams, ui, Ds_stream *);
	if (dss->fp == fp) {
	  if (do_delete) {
		ds_free(dss->ds);
		free(dss);
		dsvec_delete_ptr_index(dsv_streams, ui);
	  }
	  else
		memzap(ds_buf(dss->ds), ds_size(dss->ds));
	  break;
	}
  }
}

static void
stream_add(FILE *fp, Ds *ds)
{
  Ds_stream *dss;

  dss = ALLOC(Ds_stream);
  dss->fp = fp;
  dss->ds = ds;

  if (dsv_streams == NULL)
	dsv_streams = dsvec_init(NULL, sizeof(Ds_stream));
  dsvec_add_ptr(dsv_streams, dss);
}

/*
 * When buffered input is used (e.g., getc()), sensitive material
 * may be buffered by the stream.  By using this function instead of
 * fopen(), we can allocate our own buffer and erase it when necessary,
 * such as when ds_fclose() is called.
 */
FILE *
ds_fopen_secure(const char *path, const char *mode, size_t size)
{
  FILE *fp;
  Ds *buffer;

  if ((fp = fopen(path, mode)) == NULL)
	return(NULL);

  buffer = ds_init_size(NULL, size);
  buffer->clear_flag = 1;
  setbuffer(fp, ds_buf(buffer), size);

  stream_add(fp, buffer);

  return(fp);
}

int
ds_fclose(FILE *fp)
{

  stream_cleanup(fp, 1);
  return(fclose(fp));
}

void
ds_fclean(FILE *fp)
{

  stream_cleanup(fp, 0);
}

char *dsvec_errmsg = NULL;

int dsvec_default_initial_nalloc = 1;
int dsvec_default_clear_flag = 0;
int dsvec_default_nused_limit = 0;
void *(*dsvec_default_malloc_func)(size_t size) = malloc;
void (*dsvec_default_free_func)(void *ptr) = free;

static void
set_default_dsvec(Dsvec *dsv, size_t size)
{

  dsv->clear_flag = dsvec_default_clear_flag;
  dsv->exact_flag = 0;
  dsv->ptr = NULL;
  dsv->nalloc = 0;
  dsv->nused = 0;
  dsv->el_size = size;
  dsv->nused_limit = dsvec_default_nused_limit;
  dsv->malloc = dsvec_default_malloc_func;
  dsv->free = dsvec_default_free_func;
}

static Dsvec *
new_dsvec(size_t size)
{
  Dsvec *dsv;

  if ((dsv = ALLOC(Dsvec)) == NULL) {
	dsvec_errmsg = "malloc failed";
	return(NULL);
  }

  dsv->alloc_flag = 1;
  set_default_dsvec(dsv, size);

  return(dsv);
}

/*
 * Initialize for allocating a vector of items, each SIZE bytes in size.
 * XXX there should also be a dsvec_reinit() to free/reinitialize an
 * existing Dsvec more efficiently than a dsvec_free()/dsvec_init() combo
 */
Dsvec *
dsvec_init(Dsvec *odsv, size_t size)
{
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (odsv == NULL) {
	if ((dsv = new_dsvec(size)) == NULL)
	  return(NULL);
  }
  else {
	dsv = odsv;
	dsv->alloc_flag = 0;
	set_default_dsvec(dsv, size);
  }

  return(dsv);
}

Dsvec *
dsvec_alloc(size_t size)
{
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if ((dsv = new_dsvec(size)) == NULL)
	return(NULL);

  return(dsv);
}

/*
 * Initialize for allocating a vector of items, each SIZE bytes in size,
 * and make an initial allocation of N items.
 */
Dsvec *
dsvec_init_size(Dsvec *odsv, size_t size, int n)
{
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (odsv == NULL) {
	if ((dsv = new_dsvec(size)) == NULL)
	  return(NULL);
  }
  else {
	dsv = odsv;
	dsv->alloc_flag = 0;
	set_default_dsvec(dsv, size);
  }

  if ((dsv->ptr = (void **) dsv->malloc(n * size)) == NULL) {
	dsvec_errmsg = "malloc failed";
	return(NULL);
  }
  dsv->nalloc = n;

  return(dsv);
}

Dsvec *
dsvec_alloc_size(size_t size, int n)
{
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if ((dsv = new_dsvec(size)) == NULL)
	return(NULL);

  if ((dsv->ptr = (void **) dsv->malloc(size * n)) == NULL) {
	dsvec_errmsg = "malloc failed";
	free(dsv);
	return(NULL);
  }
  dsv->nalloc = n;

  return(dsv);
}

void
dsvec_free(Dsvec *dsv)
{

  if (dsv != NULL) {
	if (dsv->ptr != NULL) {
	  if (dsv->clear_flag)
		memzap(dsv->ptr, dsv->nalloc * dsv->el_size);
	  dsv->free(dsv->ptr);
	}

	if (dsv->alloc_flag)
	  free(dsv);
  }
}

/*
 * Extend the number of slots in DSV to NEW_NELEMENTS elements, if necessary.
 * This creates a new vector allocation and frees the old one.
 * Return 0 if ok, -1 otherwise.
 */
static int
growvec(Dsvec *dsv, unsigned int new_nelements)
{
  void **nptr;

  dsvec_errmsg = NULL;
  if (new_nelements <= dsv->nalloc)
	return(0);

  nptr = (void **) dsv->malloc((size_t) (dsv->el_size * new_nelements));
  if (nptr == NULL) {
	dsvec_errmsg = "malloc failed";
	return(-1);
  }

  memmove(nptr, dsv->ptr, (size_t) (dsv->nused * dsv->el_size));
  if (dsv->nused) {
	if (dsv->clear_flag)
	  memzap(dsv->ptr, (size_t) (dsv->nused * dsv->el_size));
	dsv->free(dsv->ptr);
  }
  dsv->ptr = nptr;
  dsv->nalloc = new_nelements;

  return(0);
}

Dsvec *
dsvec_grow(Dsvec *dsv, unsigned int new_nelements)
{

  dsvec_errmsg = NULL;
  if (dsv->nalloc >= new_nelements) {
	ds_errmsg = "invalid new_nelements for dsvec_grow()";
	return(NULL);
  }

  if (growvec(dsv, new_nelements) == -1)
	return(NULL);

  return(dsv);
}

/*
 * Read PATHNAME and parse it into newline-terminated (except for
 * possibly the last one) lines.
 */
Dsvec *
dsvec_load_lines(Dsvec *dsv, char *pathname)
{
  Ds *ds;
  Dsvec *ndsv;

  if ((ds = ds_load_file(NULL, pathname)) == NULL)
	return(NULL);

  ndsv = dsvec_lines(dsv, ds_buf(ds));

  return(ndsv);
}

/*
 * Create a copy of OLD_DSV, starting at pointer offset START (zero or greater)
 * and extending for LEN pointers (if LEN is -1, copy until the end of OLD_DSV).
 * Return NULL if the selected region of OLD_DSV is invalid.
 */
Dsvec *
dsvec_subset(Dsvec *odsv, Dsvec *old_dsv, unsigned int start, int len)
{
  int nptrs, new_nused;
  unsigned int start_offset;
  size_t need;
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (old_dsv == NULL)
	return(NULL);

  /* Validate the specified length. */
  if (len <= 0) {
	if (len == -1)
	  nptrs = old_dsv->nused;
	else
	  return(NULL);
  }
  else
	nptrs = len;

  /* Validate the starting point and the extent of the region. */
  if ((start + nptrs) > old_dsv->nused)
	return(NULL);

  if (odsv == NULL) {
	if ((dsv = ALLOC(Dsvec)) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  dsv->alloc_flag = old_dsv->alloc_flag;
  dsv->clear_flag = old_dsv->clear_flag;
  dsv->exact_flag = old_dsv->exact_flag;
  dsv->el_size = old_dsv->el_size;

  dsv->nused = nptrs;
  dsv->nalloc = nptrs;
  dsv->nused_limit = old_dsv->nused_limit;
  dsv->malloc = old_dsv->malloc;
  dsv->free = old_dsv->free;

  need = dsv->el_size * dsv->nalloc;
  if ((dsv->ptr = (void **) dsv->malloc(need)) == NULL) {
	dsvec_errmsg = "malloc failed";
	return(NULL);
  }

  start_offset = dsv->el_size * start;
  memcpy(dsv->ptr, old_dsv->ptr + start_offset, need);

  return(dsv);
}

/*
 * Create a copy of OLD_DSV.
 */
Dsvec *
dsvec_copy(Dsvec *odsv, Dsvec *old_dsv)
{
  size_t need;
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (old_dsv == NULL)
	return(NULL);

  if (odsv == NULL) {
	if ((dsv = ALLOC(Dsvec)) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  dsv->alloc_flag = old_dsv->alloc_flag;
  dsv->clear_flag = old_dsv->clear_flag;
  dsv->exact_flag = old_dsv->exact_flag;
  dsv->el_size = old_dsv->el_size;
  dsv->nused = old_dsv->nused;
  dsv->nalloc = old_dsv->nalloc;
  dsv->nused_limit = old_dsv->nused_limit;
  dsv->malloc = old_dsv->malloc;
  dsv->free = old_dsv->free;

  need = dsv->el_size * dsv->nalloc;
  if ((dsv->ptr = (void **) dsv->malloc(need)) == NULL) {
	dsvec_errmsg = "malloc failed";
	return(NULL);
  }

  memcpy(dsv->ptr, old_dsv->ptr, need);

  return(dsv);
}

void *
dsvec_ptr_index(Dsvec *dsv, unsigned int ind)
{

  if (ind >= dsv->nused)
	return(NULL);
  return(dsv->ptr[ind]);
}

/*
 * Like dsvec_ptr_index(), but "reversed" in that RIND is the offset from
 * the end rather than start.  So RIND==0 means the last element,
 * RIND==1 means the next to last element, and so on.  This can be useful
 * when comparing two vectors from their tails.
 */
void *
dsvec_ptr_rindex(Dsvec *dsv, unsigned int rind)
{
  int offset;

  offset = dsv->nused - rind - 1;
  if (offset < 0)
	return(NULL);
  return(dsv->ptr[offset]);
}

/*
 * Prepare DSV so that it can hold at least NELEMENTS more elements.
 * Return 0 if ok, -1 if it fails.
 */
static int
prepare_vec(Dsvec *dsv, unsigned int nelements)
{
  unsigned int needed_nalloc, new_nalloc;

  needed_nalloc = dsv->nused + nelements;
  if (dsv->nused_limit > 0 && needed_nalloc >= dsv->nused_limit) {
	dsvec_errmsg = "max len exceeded";
	return(-1);
  }

  if (needed_nalloc > dsv->nalloc) {
	if (dsv->nalloc == 0)
	  new_nalloc = needed_nalloc;
	else {
	  if (dsv->exact_flag)
		new_nalloc = needed_nalloc;
	  else {
#ifdef NOTDEF
		new_nalloc = dsv->nalloc + nelements * 2;
#else
		unsigned int fast_grow_nelements;

		/*
		 * To improve efficiency, preallocate vector slots.
		 * This could, of course, waste a lot of slots - if that's a problem,
		 * the slots should be created at initialization time using
		 * dsvec_init_size().
		 */
		if (nelements >= dsv->nalloc)
		  fast_grow_nelements = nelements * 2;
		else
		  fast_grow_nelements = dsv->nalloc;

		new_nalloc = dsv->nalloc + fast_grow_nelements;
	  }
#endif
	}
	if (growvec(dsv, new_nalloc) == -1) {
	  dsvec_errmsg = "could not grow vector";
	  return(-1);
	}
  }

  return(0);
}

/*
 * Delete the pointer element of DSV at index IND, bubbling up any elements
 * that follow.
 */
int
dsvec_delete_ptr_index(Dsvec *dsv, unsigned int ind)
{
  unsigned int i;

  if (dsv == NULL || ind > dsv->nused)
	return(-1);

  for (i = ind; i < dsv->nused - 1; i++)
	dsv->ptr[i] = dsv->ptr[i + 1];

  dsv->ptr[i] = NULL;
  dsv->nused--;

  return(0);
}

/*
 * Delete the first pointer element of DSV equal to PTR.
 * Any subsequent instances are not affected.
 */
int
dsvec_delete_ptr(Dsvec *dsv, void *ptr)
{
  unsigned int ind;

  if (dsv == NULL)
	return(-1);

  for (ind = 0; ind < dsv->nused; ind++) {
	if (dsv->ptr[ind] == ptr)
	  return(dsvec_delete_ptr_index(dsv, ind));
  }

  return(-1);
}

/*
 * Replace the element at offset IND from the start of DSV with PTR.
 * IND must be between 0 and dsvec_len(DSV) - 1, inclusive.
 */
int
dsvec_replace_ptr(Dsvec *dsv, void *ptr, unsigned int ind)
{

  if (dsv == NULL || ind >= dsvec_len(dsv))
	return(-1);

  dsv->ptr[ind] = ptr;

  return(0);
}

/*
 * Compare two vectors of strings for equality.
 * Return 1 if they are identical, 0 otherwise.
 */
int
dsvec_streq(Dsvec *dsv1, Dsvec *dsv2)
{
  unsigned int i;

  if (dsvec_len(dsv1) != dsvec_len(dsv2))
	return(0);

  for (i = 0; i < dsvec_len(dsv1); i++) {
	char *p, *q;

	p = (char *) dsvec_ptr_index(dsv1, i);
	q = (char *) dsvec_ptr_index(dsv2, i);
	if (!streq(p, q))
	  return(0);
  }

  return(1);
}

/*
 * A special case version of strsplit() that destructively carves BUF
 * into newline-terminated lines (except for possibly the final line).
 * Return NULL if there are no lines, otherwise either DSV or a new vector.
 */
Dsvec *
dsvec_lines(Dsvec *dsv, char *buf)
{
  char *e, *p;
  Dsvec *av;

  if (buf == NULL)
	return(NULL);

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

  p = buf;
  while ((e = strchr(p, (int) '\n')) != NULL) {
	*e++ = '\0';
	dsvec_add_ptr(av, p);
	p = e;
  }

  if (*p != '\0')
	dsvec_add_ptr(av, p);

  return(av);
}

/*
 * Rotate the elements of DSV NPOS places forward (if positive) or backward
 * (if negative), in place.
 * Return the number of positions shifted forward.
 */
int
dsvec_rotate(Dsvec *dsv, int npos)
{
  unsigned int i, j, n;
  size_t len;
  void *save;

  if ((len = dsvec_len(dsv)) == 0)
	return(0);

  if (npos < 0)
	n = len + npos;
  else
	n = npos;

  n %= len;
  if (n == 0)
	return(0);

  for (i = 0; i < n; i++) {
	save = dsv->ptr[len - 1];
	for (j = len - 1; j > 0; j--)
	  dsv->ptr[j] = dsv->ptr[j - 1];
	dsv->ptr[0] = save;
  }

  return(n);
}

/*
 * Delete (dequeue) the first COUNT elements of DSV (which moves any elements
 * that follow "forward").
 * Return 0 if ok, -1 if there are fewer than COUNT elements in DSV.
 */
int
dsvec_shift(Dsvec *dsv, int count)
{
  unsigned int i;
  size_t len;

  if (count == 0)
	return(0);

  len = dsvec_len(dsv);
  if (count < 0 || count > len)
	return(-1);

  for (i = 0; i < len - count; i++)
	dsv->ptr[i] = dsv->ptr[i + count];

  while (i < len)
	dsv->ptr[i++] = NULL;
			 
  dsv->nused -= count;

  return(0);
}

/*
 * Compare the first LEN elements of two vectors of strings for equality.
 * Return 1 if they are identical, 0 otherwise.
 */
int
dsvec_strneq(Dsvec *dsv1, Dsvec *dsv2, size_t len)
{
  unsigned int i;

  if (dsvec_len(dsv1) < len || dsvec_len(dsv2) < len)
	return(0);

  for (i = 0; i < len; i++) {
	char *p, *q;

	p = (char *) dsvec_ptr_index(dsv1, i);
	q = (char *) dsvec_ptr_index(dsv2, i);
	if (!streq(p, q))
	  return(0);
  }

  return(1);
}

/*
 * Create (or append to) a vector of pointers to strings.
 * STR_SPEC is a comma-separated list of elements; a comma can be escaped
 * by a backslash.
 */
Dsvec *
dsvec_strlist(Dsvec *odsv, const char *str_spec)
{
  char *buf;
  const char *p;
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (odsv == NULL) {
	if ((dsv = new_dsvec(sizeof(char *))) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  p = str_spec;
  buf = (char *) malloc(strlen(str_spec) + 1);

  while (*p != '\0') {
	char *b;

	b = buf;
	while (*p != ',' && *p != '\0') {
	  if (*p == '\\' && *(p + 1) == ',')
		p++;
	  *b++ = *p++;
	}
	*b = '\0';
	dsvec_add_ptr(dsv, strdup(buf));
	if (*p == ',')
	  p++;
  }

  return(dsv);
}

/*
 * Assuming DSV is a vector of character strings, write them to OFP
 * (if OFP is NULL, then stderr) one per line.
 */
void
dsvec_str_emit(FILE *ofp, Dsvec *dsv)
{
  int i;
  FILE *fp;

  if (ofp == NULL)
	fp = stderr;
  else
	fp = ofp;

  for (i = 0; i < dsvec_len(dsv); i++)
	fprintf(fp, "%s\n", (char *) dsvec_ptr_index(dsv, i));

}

/*
 * Select (up to) LEN elements of PRIMARY starting at offset IND.
 * IND must be between 0 and dsvec_len(PRIMARY) - 1, inclusive.
 * LEN may be larger than the actual number of elements available.
 */
Dsvec *
dsvec_slice(Dsvec *odsv, Dsvec *primary, unsigned int ind, unsigned int len)
{
  unsigned int i, n;
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (odsv == NULL) {
	if ((dsv = new_dsvec(sizeof(char *))) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  if (ind > dsvec_len(primary))
	return(NULL);

  n = dsvec_len(primary);
  if (n > len)
	n = len;

  for (i = ind; i < ind + n; i++)
	dsvec_add_ptr(dsv, primary->ptr[i]);

  return(dsv);
}

/*
 * Select elements of PRIMARY according to the indexes specified by RANGE_SPEC.
 * Append to ODSV if non-NULL, otherwise create a new vector.
 * Note that the first element of PRIMARY is numbered zero.
 */
Dsvec *
dsvec_range(Dsvec *odsv, Dsvec *primary, char *range_spec, Range_syntax *ors)
{
  int i, rc;
  Dsvec *dsv;
  Range_syntax *rs;

  dsvec_errmsg = NULL;
  if (primary == NULL)
	return(NULL);

  if (odsv == NULL) {
	if ((dsv = new_dsvec(sizeof(char *))) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  if (ors == NULL) {
	rs = range_set_default_syntax(NULL);
	rs->span_sep_str = "-";
	rs->signed_values = 0;
  }
  else
	rs = ors;

  /*
   * This is inefficient if PRIMARY is long, but the alternative is a little
   * more complicated and may not be worthwhile for short PRIMARY vectors;
   * convert RANGE_SPEC into a variable-length vector
   * of start/end pairs and then traverse it looking for elements and ranges
   * that select indexes between 0 and dsvec_len(PRIMARY) - 1.
   */
  for (i = 0; i < dsvec_len(primary); i++) {
	if ((rc = range_test(i, range_spec, rs, NULL)) == 1)
	  dsvec_add_ptr(dsv, dsvec_ptr_index(primary, i));
	else if (rc == -1)
	  return(NULL);
  }

  return(dsv);
}

/*
 * Delete DELETE_LEN pointers from PRIMARY, starting at offset IND from the
 * start of PRIMARY, and insert the elements of SECONDARY starting at offset
 * IND.
 * SECONDARY can be NULL or zero-length, which means that there is nothing
 * to substitute (only the deletion is needed).
 * If DELETE_LEN is zero, then nothing is being deleted; SECONDARY is being
 * inserted at IND.
 * IND must be between 0 and dsvec_len(PRIMARY), inclusive.
 * It is not an error of DELETE_LEN is larger than the actual number of
 * elements available to delete.
 */
int
dsvec_replace(Dsvec *primary, unsigned int ind, unsigned int delete_len,
			  Dsvec *secondary)
{
  int diff;
  unsigned int i, j, ndelete;

  if (ind > dsvec_len(primary))
	return(-1);

  if ((ind + delete_len) > dsvec_len(primary))
	ndelete = dsvec_len(primary) - ind;
  else
	ndelete = delete_len;

  diff = dsvec_len(secondary) - ndelete;

  if (diff > 0) {
	int tail_ind;

	/* PRIMARY will grow by DIFF; shift down, then over-write */
	if (prepare_vec(primary, diff) == -1)
	  return(-1);

	/* Shift down, if necessary. */
	if (primary->nused) {
	  tail_ind = ind + ndelete;
	  for (i = primary->nused - 1; i >= tail_ind; i--) {
		primary->ptr[i + diff] = primary->ptr[i];
		if (i == 0)
		  break;
	  }
	}

	/* Copy in new pointers. */
	for (j = 0; j < dsvec_len(secondary); j++)
	  primary->ptr[ind + j] = secondary->ptr[j];

	primary->nused += diff;
  }
  else if (diff < 0) {
	/* PRIMARY will shrink by DIFF; over-write elements, then delete */
	for (i = ind; i < dsvec_len(secondary); i++)
	  primary->ptr[i] = secondary->ptr[i];

	/* Bubble up */
	for (i = ind + ndelete, j = ind; i < primary->nused; i++, j++)
	  primary->ptr[j] = primary->ptr[i];

	primary->nused += diff;
  }
  else {
	/* The length of PRIMARY will not change */
	for (i = ind; i < dsvec_len(secondary); i++)
	  primary->ptr[i] = secondary->ptr[i];
  }

  return(primary->nused);
}

/*
 * Append SECONDARY to PRIMARY.
 */
int
dsvec_append(Dsvec *primary, Dsvec *secondary)
{

  return(dsvec_replace(primary, dsvec_len(primary), 0, secondary));
}

/*
 * Insert pointer PTR at offset IND from the start of DSV
 * IND must be between 0 and dsvec_len(DSV), inclusive.
 */
int
dsvec_insert_ptr(Dsvec *dsv, unsigned int ind, void *ptr)
{
  Dsvec d;

  dsvec_init(&d, dsv->el_size);
  dsvec_add_ptr(&d, ptr);

  return(dsvec_replace(dsv, ind, 0, &d));
}

int
dsvec_prepend_ptr(Dsvec *dsv, void *ptr)
{

  return(dsvec_insert_ptr(dsv, 0, ptr));
}

/*
 * Append PTR to the vector, extending the vector if necessary.
 * Return the index corresponding to PTR, or -1 on error.
 */
int
dsvec_add_ptr(Dsvec *dsv, void *ptr)
{

  if (prepare_vec(dsv, 1) == -1)
	return(-1);

  dsv->ptr[dsv->nused] = ptr;

  return(dsv->nused++);
}

/*
 * Modify PRIMARY by inserting SECONDARY at offset IND from the start of
 * PRIMARY.
 * IND must be between 0 and dsvec_len(PRIMARY), inclusive.
 * After insertion, element zero of SECONDARY will be element IND of PRIMARY.
 */
int
dsvec_insert(Dsvec *primary, unsigned int ind, Dsvec *secondary)
{

  return(dsvec_replace(primary, ind, 0, secondary));
}

/*
 * Delete NDELETE consecutive elements from DSV, starting at offset IND
 * from the start of DSV.
 * IND must be between 0 and dsvec_len(PRIMARY) - 1, inclusive.
 * It is not an error of NDELETE is larger than the actual number of elements
 * available to delete.
 */
int
dsvec_delete(Dsvec *dsv, unsigned int ind, unsigned int ndelete)
{

  return(dsvec_replace(dsv, ind, ndelete, NULL));
}

/*
 * Add room for one more object in DSV.
 */
int
dsvec_add_obj(Dsvec *dsv)
{
  unsigned int need;

  if (dsv->nused_limit > 0 && dsv->nused == dsv->nused_limit) {
	dsvec_errmsg = "max len exceeded";
	return(-1);
  }

  if (dsv->nused == dsv->nalloc) {
	if (dsv->nalloc == 0)
		need = dsvec_default_initial_nalloc;
	else {
	  if (dsv->exact_flag)
		need = dsv->nalloc + 1;
	  else
		need = dsv->nalloc * 2;
	}

	if (growvec(dsv, need) == -1) {
	  dsvec_errmsg = "could not grow vector";
	  return(-1);
	}
  }

  return(dsv->nused++);
}

/*
 * Create (or append to) a vector of pointers to strings, LIST.
 * LIST is either NULL or terminated by a NULL element.
 */
Dsvec *
dsvec_list(Dsvec *odsv, char **list)
{
  int i;
  char *p;
  Dsvec *dsv;

  dsvec_errmsg = NULL;
  if (odsv == NULL) {
	if ((dsv = new_dsvec(sizeof(char *))) == NULL) {
	  dsvec_errmsg = "malloc failed";
	  return(NULL);
	}
  }
  else
	dsv = odsv;

  if (list == NULL || list[0] == NULL)
	return(dsv);

  for (i = 0; list[i] != NULL; i++)
	dsvec_add_ptr(dsv, list[i]);

  return(dsv);
}

/*
 * String comparison, as might be called by qsort(3).
 */
int
dsvec_compar_string(const void *ap, const void *bp)
{
  char **a, **b;

  a = (char **) ap;
  b = (char **) bp;

  return(strcmp(*a, *b));
}

/*
 * Case-insensitive string comparison, as might be called by qsort(3).
 */
int
dsvec_compar_string_nocase(const void *ap, const void *bp)
{
  char **a, **b;

  a = (char **) ap;
  b = (char **) bp;

  return(strcasecmp(*a, *b));
}

/*
 * If EL is found (based on COMPAR returning 0) as an element in DSV
 * (from first to last), return its index (starting at 1!), otherwise return 0.
 * If COMPAR is NULL, use the default, a qsort() style string comparison
 * function.
 */
int
dsvec_find(Dsvec *dsv, void *el, int (*compar)(const void *, const void *))
{
  int st;
  unsigned int i;
  int (*c)(const void *, const void *);

  if (compar == NULL)
	c = dsvec_compar_string;
  else
	c = compar;

  for (i = 0; i < dsvec_len(dsv); i++) {
	if ((st = c(&el, &dsv->ptr[i])) == 0)
	  return(i + 1);
  }

  return(0);
}

/*
 * Return a new vector of elements from DSV that are matched by EL
 * using comparison function COMPAR: if COMPAR returns less than zero,
 * stop and return NULL; if it returns 0, ignore the element; if it returns
 * a positive value, select the element.
 *
 * If COMPAR is NULL, a default function - dsvec_select_compar() - is called,
 * and if COMPAR_INIT is also NULL, dsvec_select_init() is used.
 *
 * If COMPAR_INIT is non-NULL, it is an initialization function that is
 * called with EL; the value that it returns is passed as the third argument
 * to COMPAR; if COMPAR_INIT is NULL, NULL is passed to COMPAR.
 *
 * For example, EL might be a regular expression and COMPAR might test whether
 * it (the first argument) matches a string (the second argument).
 * COMPAR_INIT might compile the regular expression so that the resulting
 * value can be passed with each call to COMPAR (the third argument),
 * avoiding having to repeatedly compile the same regular expression.
 */
Dsvec *
dsvec_select(Dsvec *dsv, void *el, void *(*compar_init)(const void *),
			 void *compar_init_arg,
			 int (*compar)(const void *, const void *, void *))
{
  int st;
  unsigned int i;
  void *compar_arg;
  Dsvec *sel;
  int (*c)(const void *, const void *, void *);

  if (compar_init != NULL)
	compar_arg = (compar_init_arg == NULL)
	  ? compar_init(el) : compar_init(compar_init_arg);
  else {
	if (compar == NULL)
	  compar_arg = (compar_init_arg == NULL)
		? dsvec_select_init(el) : dsvec_select_init(compar_init_arg);
	else
	  compar_arg = NULL;
  }

  if (compar == NULL)
	c = dsvec_select_compar;
  else
	c = compar;

  sel = NULL;
  for (i = 0; i < dsvec_len(dsv); i++) {
	if ((st = c(el, dsv->ptr[i], compar_arg)) < 0)
	  return(NULL);
	if (st != 0) {
	  if (sel == NULL)
		sel = dsvec_init(NULL, dsvec_size(dsv));
	  dsvec_add_ptr(sel, dsv->ptr[i]);
	}
  }

  return(sel);
}

/*
 * Compare element EL to element WW, returning a negative value, zero, or
 * a positive value according to whether EL is less than, equal to, or greater
 * than WW, respectively.
 * Also, set PREFIX_LEN (if non-NULL) to the number of initial characters the
 * two elements have in common.
 * CONF is a generic configuration argument; for this comparison function,
 * it points to an integer that is non-zero iff case-insensitive comparisons
 * should be used.
 */
static int
default_compar(void *el, void *ww, void *conf, size_t *prefix_len)
{
  int ch_e, ch_w, flags, n;
  char *e, *w;

  e = (char *) el;
  w = (char *) ww;
  if (conf == NULL)
	flags = 0;
  else
	flags = *(int *) conf;

  n = 0;
  while (1) {
	if (flags) {
	  ch_e = tolower((int) *e++);
	  ch_w = tolower((int) *w++);
	}
	else {
	  ch_e = *e++;
	  ch_w = *w++;
	}

	if (ch_w != ch_e)
	  break;
	if (ch_w == '\0')
	  break;
	n++;
  }

  if (prefix_len != NULL)
	*prefix_len = n;

  return(ch_w - ch_e);
}

/*
 * Binary search
 * Search for WW within ordered vector of elements DSV, calling comparison
 * function COMPAR with argument CONF.  The comparison function returns a value
 * less than zero, zero, or greater than zero according to whether WW is to
 * be considered, respectively, less than, equal to, or greater than a given
 * element of DSV.
 * If WW is found, return its index in DSV (between zero and dsvec_len(dsv) - 1,
 * inclusive); otherwise, return a negative value; the caller can obtain the
 * index of where WW should be inserted into DSV by taking the absolute value
 * of the return value and subtracting one.
 */
int
dsvec_binary_search(Dsvec *dsv, void *ww,
					int (*compar)(void *, void *, void *, size_t *),
					void *conf)
{
  int low, high, mid, cond;
  int (*c)(void *, void *, void *, size_t *);
  void *el;

  if (compar == NULL)
	c = default_compar;
  else
	c = compar;

  low = 0;
  if ((high = dsvec_len(dsv) - 1) < 0)
	return(-1);
  mid = 0;

  while (low <= high) {
	mid = (low + high) / 2;
	el = dsvec_ptr_index(dsv, mid);

	if ((cond = (*c)(el, ww, conf, NULL)) < 0)
	  high = mid - 1;
	else if (cond > 0)
	  low = mid + 1;
	else
	  return(mid);
  }

  /*
   * If the returned value is negative, the caller takes the absolute value
   * and subtracts one to get the expected location.
   */
  if (high < 0) {
	/* Word belongs at the start: (abs(-1) - 1) == 0 */
	return(-1);
  }

  if (low >= dsvec_len(dsv)) {
	/*
	 * Word belongs at the end: if dsvec_len(dsv)==10, then return -11
	 * so that (abs(-11) - 1) == 10
	 */
	return(-dsvec_len(dsv) - 1);
  }

  /*
   * Word belongs within the list: if low==1, return -2 so that
   * (abs(-2) - 1) == 1 (the second element)
   */
  return(-(low + 1));
}

/*
 * Word completion
 * Given an ordered list of "words" (actually, just strings) DSV, find the
 * starting and ending indices, if any, of elements that begin with the prefix
 * WORD.  COMPAR, if not NULL, is a function that is called (with two pointers
 * to objects to compare, a generic argument CONF, and a pointer argument)
 * to compare WORD to an element in DSV; if COMPAR is NULL, the default
 * function is used (which uses case-sensitive string comparisons) - this is
 * the same as the COMPAR function to dsvec_binary_search().  When possible, the
 * function must also use the final pointer argument to return the length
 * of the common prefix of the first two arguments.
 *
 * Return the number of completions; if positive, also set COMPLETIONS to
 * identify the indices, the length of the common prefix, and the number of
 * characters following the prefix that all completions have in common
 * (i.e., the maximum number of characters that WORD can be extended
 * such that it will still match all of the potential completions, specifically
 * the first PREFIX_CNT + SKIP_CNT characters of any potential completion).
 * See search_test() for an example.
 *
 * Completion is useful in non-graphical user interfaces (such as by shells
 * for command and filename completion) or when a graphical display might be
 * impractical or clunky (such as when requiring a user to supply a
 * person's valid lastname out of a large directory).
 * Also see: http://developer.yahoo.com/yui/autocomplete/
 */
int
dsvec_complete(Dsvec *dsv, char *word,
			   int (*compar)(void *w1, void *w2, void *conf, size_t *plen),
			   void *conf, Completions *completions)
{
  int i, len, max_cnt, nwords, st, start_offset;
  char *p;
  size_t prefix_len;
  int (*c)(void *, void *, void *, size_t *);

  if (compar == NULL)
	c = default_compar;
  else
	c = compar;

  completions->start = -1;
  completions->end = -1;
  completions->prefix_cnt = 0;
  completions->skip_cnt = 0;

  /*
   * Determine where WORD is (or should be) in WORDVEC.
   *
   * XXX can this be generalized? instead of having start/end indexes,
   * what if there was an ordered list of indexes (or pointers to elements);
   * that is, instead of the results being a contiguous range within DSV,
   * return a (possibly empty) subset of DSV.
   * For instance, if WORD were the regex ".*man", we might want to
   * complete it as "airman", "bagman", "man", "woodman"
   * Of course, this capability suggests that some or all of the elements
   * of DSV might need to be examined
   */
  nwords = dsvec_len(dsv);
  if ((st = dsvec_binary_search(dsv, word, compar, conf)) >= 0)
	start_offset = st;
  else {
	start_offset = -st - 1;
	if (start_offset >= nwords)
	  return(0);
  }

  /* Find the first and last elements that have WORD as a prefix. */
  len = strlen(word);
  for (i = start_offset; i < nwords; i++) {
	void *w;

	w = (char *) dsvec_ptr_index(dsv, i);
	st = c(w, word, conf, &prefix_len);
	if (prefix_len == len) {
	  if (completions->start < 0) {
		completions->start = completions->end = i;
		completions->prefix_cnt = len;
	  }
	  else
		completions->end = i;
	}
	else if (st < 0 || completions->start >= 0)
	  break;
  }

  max_cnt = -1;
  for (i = completions->start; i < completions->end; i++) {
	void *w1, *w2;

	w1 = (void *) dsvec_ptr_index(dsv, i);
	w2 = (void *) dsvec_ptr_index(dsv, i + 1);
	c(w1, w2, conf, &prefix_len);
	if (max_cnt == -1)
	  max_cnt = prefix_len;
	else if (prefix_len < max_cnt)
	  max_cnt = prefix_len;
  }
  if (completions->start == completions->end || max_cnt == -1)
	completions->skip_cnt = 0;
  else
	completions->skip_cnt = max_cnt - completions->prefix_cnt;

  return((completions->start < 0)
		 ? 0 : (completions->end - completions->start + 1));
}

/*
 * An extended initializer for dsvec_select().
 * Note that by default, matches are *not* anchored.
 */
void *
dsvec_select_initx(const void *arg)
{
  int st;
  char errbuf[100], *regex_str;
  regex_t *preg;
  Select_arg *sa;

  sa = (Select_arg *) arg;
  if (sa->regex == NULL || sa->regex == '\0')
	return(NULL);

  if (sa->anchored)
	regex_str = ds_xprintf("^%s", sa->regex);
  else
	regex_str = sa->regex;

  sa->preg = (regex_t *) malloc(sizeof(regex_t));
  if ((st = regcomp(sa->preg, regex_str, sa->regex_flags)) != 0) {
	regerror(st, sa->preg, sa->errbuf, sa->errbuf_size);
	return(NULL);
  }

  return(sa->preg);
}

/*
 * A default initializer for dsvec_select().
 * Note that by default, matches are *not* anchored.
 */
void *
dsvec_select_init(const void *arg)
{
  int st;
  char errbuf[100], *regex_str;
  regex_t *preg;

  regex_str = (char *) arg;
  if (regex_str == NULL || *regex_str == '\0')
	return(NULL);

  preg = (regex_t *) malloc(sizeof(regex_t));
  if ((st = regcomp(preg, regex_str, REG_EXTENDED | REG_NOSUB)) != 0) {
	regerror(st, preg, errbuf, sizeof(errbuf));
	return(NULL);
  }

  return(preg);
}

/*
 * A default comparison function for dsvec_select()
 * Return 1 if A matches B, 0 if it does not, or -1 if an error occurs.
 */
int
dsvec_select_compar(const void *a, const void *b, void *compar_arg)
{
  int st;
  char *str;
  regex_t *preg;
  regmatch_t pmatch[1];

  preg = (regex_t *) compar_arg;
  str = (char *) b;

  st = regexec(preg, str, 1, pmatch, 0);
  if (st == 0)
	return(1);
  if (st == REG_NOMATCH)
	return(0);
  return(-1);
}

typedef struct Test_data {
  char *state;
  char *abbrev;
} Test_data;

static Test_data test_data[] = {
  /* 0*/ { "Alabama", "AL" },        { "Alaska", "AK" },
  /* 2*/ { "Arizona", "AZ" },        { "Arkansas", "AR" },
  /* 4*/ { "California", "CA" },     { "Colorado", "CO" },
  /* 6*/ { "Connecticut", "CT" },    { "Delaware", "DE" },
  /* 8*/ { "District of Columbia", "DC" }, { "Florida", "FL" },
  /*10*/ { "Georgia", "GA" },        { "Hawaii", "HI" },
  /*12*/ { "Idaho", "ID" },          { "Illinois", "IL" },
  /*14*/ { "Indiana", "IN" },        { "Iowa", "IA" },
  /*16*/ { "Kansas", "KS" },         { "Kentucky", "KY" },
  /*18*/ { "Louisiana", "LA" },      { "Maine", "ME" },
  /*20*/ { "Maryland", "MD" },       { "Massachusetts", "MA" },
  /*22*/ { "Michigan", "MI" },       { "Minnesota", "MN" },
  /*24*/ { "Mississippi", "MS" },    { "Missouri", "MO" },
  /*26*/ { "Montana", "MT" },        { "Nebraska", "NE" },
  /*28*/ { "Nevada", "NV" },         { "New Hampshire", "NH" },
  /*30*/ { "New Jersey", "NJ" },     { "New Mexico", "NM" },
  /*32*/ { "New York", "NY" },       { "North Carolina", "NC" },
  /*34*/ { "North Dakota", "ND" },   { "Ohio", "OH" },
  /*36*/ { "Oklahoma", "OK" },       { "Oregon", "OR" },
  /*38*/ { "Pennsylvania", "PA" },   { "Rhode Island", "RI" },
  /*40*/ { "South Carolina", "SC" }, { "South Dakota", "SD" },
  /*42*/ { "Tennessee", "TN" },      { "Texas", "TX" },
  /*44*/ { "Utah", "UT" },           { "Vermont", "VT" },
  /*46*/ { "Virginia", "VA" },       { "Washington", "WA" },
  /*48*/ { "West Virginia", "WV" },  { "Wisconsin", "WI" },
  /*50*/ { "Wyoming", "WY" },        { NULL, NULL }
};

typedef struct Search_word {
  char *word;
  int flags;
  int expected_loc;
} Search_word;

/*
 * XXX these test cases should not be so hardwired...
 */
static int
search_test(FILE *fp)
{
  int i, st;
  char *p, *q;
  Completions completions;
  Ds ds;
  Dsvec *dsv, *dsv_s;
  static Search_word searchlist[] = {
	{ "Delaware", 0, 7 }, { "Abba", 0, -1 },    { "zork", 0, -52 },
	{ "Alabama", 0, 0 },  { "Wyoming", 0, 50 }, { "Michigan", 0, 22 },
	{ "idaho", 1, 12 },   { "Alarm", 0, -2 },   { NULL, 0, 0 }
  };

  dsv = dsvec_init(NULL, sizeof(char *));
  for (i = 0; test_data[i].state != NULL; i++)
	dsvec_add_ptr(dsv, test_data[i].state);

  fprintf(fp, "dsvec_find... ");
  if ((st = dsvec_find(dsv, "Montana", NULL)) != 27) {
	fprintf(fp, "failed: expected 27, got %d\n", st);
	return(-1);
  }
  fprintf(fp, "ok\n");

  fprintf(fp, "dsvec_select... ");
  dsv_s = dsvec_select(dsv, "iss", dsvec_select_init, NULL,
					   dsvec_select_compar);
  if (dsv_s == NULL || dsvec_len(dsv_s) != 2) {
	fprintf(fp, "failed: expected 2 selections\n");
	return(-1);
  }

  /* Make sure the test data is sorted. */
  dsvec_sort(dsv, NULL);

  p = (char *) dsvec_ptr_index(dsv_s, 0);
  q = (char *) dsvec_ptr_index(dsv_s, 1);
  if (!streq(p, "Mississippi") || !streq(q, "Missouri")) {
	fprintf(fp, "failed: incorrect selections\n");
	return(-1);
  }
  fprintf(fp, "ok\n");

  fprintf(fp, "Binary search:\n");
  for (i = 0; searchlist[i].word != NULL; i++) {
	st = dsvec_binary_search(dsv, (void *) searchlist[i].word, NULL,
							 (void *) &searchlist[i].flags);
	if (st == searchlist[i].expected_loc) {
	  fprintf(fp, " %2d. \"%s\"", i, searchlist[i].word);
	  if (st < 0)
		fprintf(fp, " (insert at %d)", (-st) - 1);
	  fprintf(fp, "... ok\n");
	}
	else {
	  fprintf(fp, "%2d. \"%s\": st=%d, expected=%d\n",
			  i, searchlist[i].word, st, searchlist[i].expected_loc);
	  return(-1);
	}
  }

  fprintf(fp, "Word completion list:\n");
  ds_init(&ds);
  for (i = 0; i < dsvec_len(dsv); i++) {
	p = (char *) dsvec_ptr_index(dsv, i);
	ds_asprintf(&ds, "%s\"%s\"", (i != 0) ? ", " : "", p);
  }
  fprintf(fp, "%s\n", strfmt(ds_buf(&ds), NULL, 72));

  fprintf(fp, "Complete \"So\"... ");
  st = dsvec_complete(dsv, "So", NULL, NULL, &completions);
  if (st != 2 || completions.start != 40 || completions.end != 41
	  || completions.prefix_cnt != 2 || completions.skip_cnt != 4) {
	fprintf(fp,
			"failed: expected st=2, start=40, end=41, prefix_cnt=2, skip=4\n");
	fprintf(fp,
			"got: st=%d, start=%d, end=%d, prefix_cnt=%d, skip=%d\n",
			st, completions.start, completions.end, completions.prefix_cnt,
			completions.skip_cnt);
	return(-1);
  }
  fprintf(fp, "ok\n");
  fprintf(fp, "[");
  for (i = completions.start; i < completions.end; i++)
	fprintf(fp, "\"%s\", ", test_data[i].state);
  fprintf(fp, "\"%s\"] ", test_data[i].state);
  fprintf(fp, "extend=\"%s\"\n",
		  strndup(test_data[completions.start].state,
				  completions.prefix_cnt + completions.skip_cnt));

  {
	Dsvec *dsv_ar;
	Select_arg sa;
	static char *ar_states[] = {
	  /* Ordered */
	  "Arizona", "Arkansas", "Delaware", "Maryland", "North Carolina",
	  "South Carolina", NULL
	};

	fprintf(fp, "Select \"ar\"... ");
	sa.regex = "ar";
	sa.regex_flags = REG_EXTENDED | REG_NOSUB | REG_ICASE;
	sa.anchored = 0;
	sa.errbuf = (char *) malloc(100);
	sa.errbuf_size = 100;
	dsv_s = dsvec_select(dsv, "ar", dsvec_select_initx, &sa,
						 dsvec_select_compar);
	dsv_ar = dsvec_list(NULL, ar_states);
	if (!dsvec_is_identical(dsv_s, dsv_ar, NULL)) {
	  fprintf(fp, "failed\n");
	  return(-1);
	}
	fprintf(fp, "ok\n");
	ds_reinit(&ds);
	for (i = 0; i < dsvec_len(dsv_s); i++) {
	  p = (char *) dsvec_ptr_index(dsv_s, i);
	  ds_asprintf(&ds, "%s\"%s\"", (i != 0) ? ", " : "", p);
	}
	fprintf(fp, "[%s]\n", strfmt(ds_buf(&ds), NULL, 72));
  }

  return(0);
}

/*
 * Sort the items in DSV, using qsort(3) with the specified callback comparison
 * function.  If COMPAR is NULL, use the default function, which is
 * case-sensitive string comparison given (char **) pointers.
 * Note that DSV is sorted in place.
 */
void
dsvec_sort(Dsvec *dsv, int (*compar)(const void *, const void *))
{

  if (dsv == NULL)
	return;

  if (compar == NULL)
	qsort(dsvec_base(dsv), dsvec_len(dsv), dsv->el_size, dsvec_compar_string);
  else
	qsort(dsvec_base(dsv), dsvec_len(dsv), dsv->el_size, compar);
}

/*
 * Check if every element of DSV_SUBSET is present in DSV, passing COMPAR to
 * dsvec_find() to match elements.
 * The empty set is a subset of every set.
 * Return 1 if so, 0 otherwise.
 */
int
dsvec_is_subset(Dsvec *dsv, Dsvec *dsv_subset,
				int (*compar)(const void *, const void *))
{
  int i;
  void *el;

  for (i = 0; i < dsvec_len(dsv_subset); i++) {
	el = dsvec_ptr_index(dsv_subset, i);
	if (dsvec_find(dsv, el, compar) < 1)
	  return(0);
  }

  return(1);
}

/*
 * Check if DSV_A and DSV_B are equivalent (contain the same element values,
 * without regard to ordering).
 * Return 1 if so, 0 otherwise.
 */
int
dsvec_is_equiv(Dsvec *dsv_a, Dsvec *dsv_b,
			   int (*compar)(const void *, const void *))
{

  if (dsvec_len(dsv_a) == dsvec_len(dsv_b)
	  && dsvec_is_subset(dsv_a, dsv_b, compar))
	return(1);

  return(0);
}

/*
 * Check if DSV_A and DSV_B are identical (contain the same element values
 * in the same order).
 * Return 1 if so, 0 otherwise.
 */
int
dsvec_is_identical(Dsvec *dsv_a, Dsvec *dsv_b,
				   int (*compar)(const void *, const void *))
{
  int i;
  void *el_a, *el_b;
  int (*c)(const void *, const void *);

  if (dsvec_len(dsv_a) != dsvec_len(dsv_b))
	return(0);

  if (compar == NULL)
	c = dsvec_compar_string;
  else
	c = compar;

  for (i = 0; i < dsvec_len(dsv_a); i++) {
	el_a = dsvec_ptr_index(dsv_a, i);
	el_b = dsvec_ptr_index(dsv_b, i);
	if (c(&el_a, &el_b) != 0)
	  return(0);
  }

  return(1);
}

static const char *default_startq = "\"'{";
static const char *default_endq   = "\"'}";
static const char *default_ifs = " \t";
static const int default_keepq = 0;
static const int default_keepws = 0;

static char *
copyarg(char *str, int len)
{
  char *c, *e, *p, *s;

  if ((p = c = (char *) malloc(len + 1)) == NULL)
	return(NULL);

  s = str;
  e = s + len;
  while (s < e) {
	if (*s == '\\')
	  s++;
	*p++ = *s++;
  }

  *p = '\0';
  return(c);
}

/*
 * Read lines from DS (already configured via dsio_set())
 * and return a vector of pointers, one per line.
 * If DSV is non-NULL, add the pointers to it.
 */
Dsvec *
dsvec_load(Ds *ds, Dsvec *dsv)
{
  char *line;
  Dsvec *av;

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

  while ((line = dsio_agets(ds)) != NULL) {
	dsvec_add_ptr(av, line);
	save_buf(ds);
  }

  return(av);
}

static int
add(Dsvec *dsv, char *str, int len)
{
  char *w;

  if (dsv == NULL)
	return(0);

  w = NULL;
  if (str != NULL) {
	if ((w = copyarg(str, len)) == NULL)
	  return(-1);
  }

  dsvec_add_ptr(dsv, w);

  return(0);
}

/*
 * Append a copy of element EL to the argv-style list, or create a new one.
 */
Dsvec *
ds_mkargv_add(Dsvec *dsv, char *el)
{
  Dsvec *av;

  if (dsv == NULL) {
	if ((av = dsvec_init(NULL, sizeof(char *))) == NULL)
	  return(NULL);
  }
  else {
	if (dsv->nused) {
	  /*
	   * We are appending to an existing, non-empty vector, so we must
	   * take care of the previously-added NULL entry.
	   */
	  dsv->nused--;
	}
	av = dsv;
  }

  dsvec_add_ptr(av, strdup(el));
  dsvec_add_ptr(av, NULL);

  return(av);
}

/*
 * Append copies of the first ARGC elements of ARGV to the argv-style list,
 * or create a new list.
 */
Dsvec *
ds_mkargv_addv(Dsvec *dsv, int argc, char **argv)
{
  int i;
  Dsvec *av;

  if (dsv == NULL) {
	if ((av = dsvec_init(NULL, sizeof(char *))) == NULL)
	  return(NULL);
  }
  else {
	if (dsv->nused) {
	  /*
	   * We are appending to an existing, non-empty vector, so we must
	   * take care of the previously-added NULL entry.
	   */
	  dsv->nused--;
	}
	av = dsv;
  }

  for (i = 0; i < argc; i++)
	dsvec_add_ptr(av, argv[i]);

  dsvec_add_ptr(av, NULL);

  return(av);
}

/*
 * XXX this function has expanded beyond its intended functionality and should be used with
 * care.  It is fragile and should be replaced.  The main issue is that sometimes you
 * want consecutive field separators to indicate empty fields and other times two or more
 * consecutive separators are equivalent to one separator.
 * Consider simplifying the functionality.
 *
 * See dsvec_csv().
 *
 * Parse STR into an argv/argc type vector of words.
 * A NULL entry is appended to the pointer vector.
 * So, if dsv = ds_mkargv(NULL, str, NULL), then
 * argv = (char **) dsvec_base(dsv) and argc = dsvec_len(dsv) - 1
 * If DSV is non-NULL, append the new words to any existing ones.
 */
Dsvec *
ds_mkargv(Dsvec *dsv, char *str, Mkargv *conf)
{
  int argc, endq_ch, keepq, keepws;
  char *p, *q, *s;
  const char *ifs, *startq, *endq;
  Dsvec *argv;

  if (conf != NULL) {
	keepq = conf->keepq;
	keepws = conf->keepws;
	if (conf->ifs != NULL)
	  ifs = conf->ifs;
	else
	  ifs = default_ifs;
	if (conf->startq != NULL && conf->endq != NULL) {
	  startq = conf->startq;
	  endq = conf->endq;
	}
	else {
	  startq = default_startq;
	  endq = default_endq;
	}
  }
  else {
	keepq = default_keepq;
	keepws = default_keepws;
	ifs = default_ifs;
	startq = default_startq;
	endq = default_endq;
  }

  p = str;
  argc = 0;

  if (dsv == NULL) {
	if ((argv = dsvec_init(NULL, sizeof(char *))) == NULL)
	  return(NULL);
  }
  else {
	if (dsv->nused) {
	  /*
	   * We are appending to an existing, non-empty vector, so we must
	   * take care of the previously-added NULL entry.
	   */
	  dsv->nused--;
	}
	argv = dsv;
  }

  while (1) {
	if (!keepws) {
	  /* Skip leading white space */
 	  while (*p == ' ' || *p == '\t')
	    p++;
	}

	if (*p == '\n' || *p == '\0')
	  goto done;

	/* Note that start */
	s = p;

	/* If there's an optional quote, note that we need a matching end quote */
	endq_ch = 0;
	if ((q = strchr(startq, (int) *p)) != NULL) {
	  endq_ch = endq[q - startq];
	  if (!keepq)
		s = p + 1;
	  p++;
	}

	/* Find the end of the argument */
	while (1) {
	  if (*p == '\n' || *p == '\0') {
		if (endq_ch) {
		  /* Missing closing quote */
		  goto fail;
		}

		if (add(argv, s, p - s) == -1)
		  goto fail;
		argc++;
		break;
	  }
	  else if (endq_ch && *p == endq_ch
			   && (strchr(ifs, (int) *(p + 1)) != NULL || *(p + 1) == '\n'
				   || *(p + 1) == '\0')) {
		if (add(argv, s, keepq ? (p - s + 1) : (p - s)) == -1)
		  goto fail;
		argc++;
		p++;
		break;
	  }
	  else if (*p == '\\') {
		p++;
		if (*p == '\n' || *p == '\0') {
		  /* Trailing escape character */
		  goto fail;
		}
		p++;
	  }
	  else if (!endq_ch && (strchr(ifs, (int) *p) != NULL)) {
		if (add(argv, s, p - s) == -1)
		  goto fail;
		argc++;
		p++;
		break;
	  }
	  else
		p++;
	}
  }

 done:
  /* Add a NULL entry to terminate the list. */
  add(argv, NULL, 0);

#ifdef NOTDEF
  /*
   * This kludge puts a pointer to the Argv structure in the pointer vector
   * so that mkargv_free() can also free the Argv structure
   * XXX BUT THIS THROWS OFF THE COUNT...
   * XXX this should be done by using an invisible (uncounted) initial slot
   */
  dsvec_add_ptr(argv, argv);
#endif

  return(argv);

 fail:
  /* XXX leakage */
  dsvec_free(argv);
  return(NULL);
}

/*
 * Return a copy of the next field in STR.  Any character in STR is escaped if it is preceded by
 * a backslash.
 * Any character in IFS is used to separate one field from the next; the end of string or a
 * newline are implicitly IFS characters.
 * Any character in STARTQ is a starting quote character; the corresponding character in ENDQ
 * is the matching ending quote character.
 * If INCLUDE_WS is non-zero, spaces that precedes the field are part of the field.
 * If non-NULL, set ENDP to point to the character that terminated the field, which will be
 * an IFS character, a newline, or the null character (if there are no more fields).
 */
static char *
csv_get_field(char *str, char *ifs, char *startq, char *endq, int include_ws, char **endp)
{
  int endq_ch, startq_ch;
  char *p, *q, *s;
  Ds ds;

  ds_init(&ds);

  s = str;
  if (!include_ws && *s != '\\') {
	while (*s == ' ')
	  s++;
  }

  endq_ch = 0;
  if (startq != NULL && *startq == *s) {
	if (strlen(startq) != strlen(endq))
	  return(NULL);

	if ((q = strchr(startq, (int) *s)) != NULL) {
	  startq_ch = *q;
	  endq_ch = endq[q - startq];
	  s++;
	}
  }

  p = s;
  while (*p != '\0' && *p != '\n') {
	if (*p == '\\') {
	  p++;
	  ds_appendc(&ds, (int) *p++);
	}
	else if (endq_ch && *p == endq_ch) {
	  /* Matching end quote found. */
	  p++;
	  if (*p == '\0' || *p == '\n') {
		/* This is the end of line or end of string. */
		break;
	  }

	  if (strchr(ifs, (int) *p) != NULL) {
		/* A separator is next; this is the end of the field. */
		break;
	  }

	  /*
	   * This closing quote is embedded within the field...
	   * RFC 4180 says that an embedded double quote is quoted by doubling it.
	   */
	  if (startq_ch == '"' && endq_ch == '"' && *p == '"') {
		ds_appendc(&ds, (int) '"');
		p++;
	  }
	  else {
		/* Other cases are invalid. */
		return(NULL);
	  }
	}
	else if (!endq_ch && strchr(ifs, (int) *p) != NULL) {
	  /* Found a separator. */
	  break;
	}
	else
	  ds_appendc(&ds, (int) *p++);
  }
  ds_appendc(&ds, (int) '\0');

  if (endp != NULL)
	*endp = p;

  return(ds_buf(&ds));
}

/*
 * Comma-separated value (CSV) parsing.
 * Extract zero or more fields from STR, each of which is separated from the next by an
 * IFS (internal field separator) character or terminated by a newline or the end of string.
 * Individual fields may be quoted, according to STARTQ/ENDQ, as can any character, so that
 * IFS characters can be safely embedded.  STR can consist of one or more lines.
 * This is therefore a little more functional than simple CSV parsing.
 *
 * Return a vector of fields, or NULL if an error occurs.
 * See csv_get_field(), which does most of the work.
 *
 * XXX RFC 4180
 */
Dsvec *
dsvec_csv(char *str, char *ifs, char *startq, char *endq, int include_ws)
{
  char *ptr, *s;
  Dsvec *dsv;

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

  ptr = str;
  while (*ptr != '\0') {
	if ((s = csv_get_field(ptr, ifs, startq, endq, include_ws, &ptr)) == NULL)
	  return(NULL);
	dsvec_add_ptr(dsv, s);

	if (*ptr == '\0')
	  return(dsv);

	/*
	 * Special case: if the string (or line) ends with an IFS character, then we add
	 * the implied (empty) field.
	 */
	if (strchr(ifs, (int) *ptr) != NULL && (*(ptr + 1) == '\0' || *(ptr + 1) == '\n'))
	  dsvec_add_ptr(dsv, strdup(""));
	ptr++;
  }

  return(dsv);
}

typedef struct Csv_test {
  char *str;
  char *ifs;
  char *startq;
  char *endq;
  int include_ws;
  char *expect[9];
} Csv_test;

Csv_test csv_test[] = {
  { "\"a\"\"b\", \"c\", \"d\"",   ",", "\"", "\"", 0, { "a\"b", "c", "d", NULL }},
  { "  a, b, c, d",               ",", NULL, NULL, 0, { "a", "b", "c", "d", NULL }},
  { "a\t\tbb\tccc",               "\t", NULL, NULL, 0, { "a", "", "bb", "ccc", NULL }},
  { "a\tbb\tccc",                 "\t", NULL, NULL, 0, { "a", "bb", "ccc", NULL }},
  { "a bb  ccc",                  " ", NULL, NULL, 0, { "a", "bb", "ccc", NULL }},
  { "a bb ccc",                   " ", NULL, NULL, 0, { "a", "bb", "ccc", NULL }},
  { "a'bc",                       ",", "'", "'", 0, { "a'bc", NULL }},
  { "'a\\'bc'",                   ",", "'", "'", 0, { "a'bc", NULL }},
  { "",                           ",", NULL, NULL, 0, { NULL }},
  { "   ",                        ",", NULL, NULL, 0, { "", NULL }},
  { "abc",                        ",", NULL, NULL, 0, { "abc", NULL }},
  { "a,",                         ",", NULL, NULL, 0, { "a", "", NULL }},
  { ",",                          ",", NULL, NULL, 0, { "", "", NULL }},
  { "a b c d e",                  ",", NULL, NULL, 0, { "a b c d e", NULL }},
  { "a:b:c",                      ":", NULL, NULL, 0, { "a", "b", "c", NULL }},
  { "a\\:b:c",                    ":", NULL, NULL, 0, { "a:b", "c", NULL }},
  { "abc:def:ghi",                ":", NULL, NULL, 0, { "abc", "def", "ghi", NULL }},
  { "\"abc\", \"def\",  \"ghi\"", ",", "\"", "\"", 0, { "abc", "def", "ghi", NULL }},
  { "abc, def,, ghi",             ",", NULL, NULL, 0, { "abc", "def", "", "ghi", NULL }},
  { ",abc, def,ghi,",             ",", NULL, NULL, 0, { "", "abc", "def", "ghi", "", NULL }},
  { "a\nb\nc\nd e",               ",", NULL, NULL, 0, { "a", "b", "c", "d e", NULL }},
  { NULL,                         NULL, NULL, NULL, 0, { NULL } }
};

static int
dsvec_csv_test(FILE *out)
{
  int i, tnum;
  char *endp, *str;
  Csv_test *test;
  Dsvec *dsv;

  
  for (test = &csv_test[0]; test->str != NULL; test++) {
	dsv = dsvec_csv(test->str, test->ifs, test->startq, test->endq, test->include_ws);
	if (dsv == NULL) {
	  fprintf(out, "(parse failed) ");
	  return(-1);
	}

	for (tnum = 0; test->expect[tnum] != NULL; tnum++) {
	  str = (char *) dsvec_ptr_index(dsv, tnum);
	  if (str == NULL) {
	  err:
		fprintf(out, "\nInput: \"%s\"\n", test->str);
		fprintf(out, "Expect:\n");
		for (i = 0; test->expect[i] != NULL; i++)
		  fprintf(out, "\"%s\"%s", test->expect[i], (test->expect[i+1] == NULL) ? "\n" : " ");
		fprintf(out, "Got:\n");
		for (i = 0; i < dsvec_len(dsv); i++) {
		  str = (char *) dsvec_ptr_index(dsv, i);
		  fprintf(out, "\"%s\"%s", str, (i == dsvec_len(dsv) - 1) ? "\n" : " ");
		}
		fprintf(out, "\n");
		return(-1);
	  }
	  if (!streq(str, test->expect[tnum]))
		goto err;
	}
	if (tnum != dsvec_len(dsv))
	  goto err;
  }

  return(0);
}

static void
selftest_show(FILE *fp, Dsvec *dsv)
{
  int i;
  FILE *out;

  if (fp == NULL)
	out = stderr;
  else
	out = fp;

  for (i = 0; i < dsvec_len(dsv); i++)
	fprintf(out, "%s%s", i ? "," : "", (char *) dsvec_ptr_index(dsv, i));
  fprintf(out, " [len=%u]", dsvec_len(dsv));
}

typedef enum {
  SELFTEST_APPEND  = 0,
  SELFTEST_INSERT  = 1,
  SELFTEST_DELETE  = 2,
  SELFTEST_REPLACE = 3,
  SELFTEST_SLICE   = 4
} Selftest_op;

static int
selftest(FILE *fp, Selftest_op op, Dsvec *primary, unsigned int start_ind,
		 unsigned int len, Dsvec *secondary, char *msg,
		 char *result_spec)
{
  int rc;
  Dsvec *p, *q, *s;
  /* Reset the initial vectors */
  p = dsvec_copy(NULL, primary);
  s = dsvec_copy(NULL, secondary);

  rc = 0;
  switch (op) {
  case SELFTEST_APPEND:
	rc = dsvec_append(p, s);
	break;

  case SELFTEST_DELETE:
	rc = dsvec_delete(p, start_ind, len);
	break;

  case SELFTEST_INSERT:
	rc = dsvec_insert(p, start_ind, s);
	break;

  case SELFTEST_REPLACE:
	rc = dsvec_replace(p, start_ind, len, secondary);
	break;

  case SELFTEST_SLICE:
	q = dsvec_slice(NULL, p, start_ind, len);
	if (q == NULL)
	  rc = -1;
	else
	  p = q;
	break;

  default:
	rc = -1;
	break;
  }

  fprintf(fp, "%s: ", msg);
  if (rc == -1)
	fprintf(fp, "Failed!");
  else {
	selftest_show(fp, p);

	if (result_spec != NULL) {
	  Dsvec *r;

	  r = dsvec_strlist(NULL, result_spec);
	  if (dsvec_streq(p, r))
		fprintf(fp, " [ok]");
	  else {
		fprintf(fp, " [incorrect!]");
		rc = -1;
	  }
	}
  }

  fprintf(fp, "\n");

  dsvec_free(p);
  dsvec_free(s);

  return(rc);
}

int
dsvec_selftest(FILE *fp)
{
  int i, n;
  char *a[5], *ptr;
  Ds *x, *y;
  Dsvec *dsv, primary, *p, *pc, secondary, *s, *sc;
  FILE *out;
  Mkargv mkargv_conf;

  if (fp == NULL)
	out = stderr;
  else
	out = fp;

  p = dsvec_init(&primary, sizeof(char *));
  s = dsvec_init(&secondary, sizeof(char *));
  dsvec_add_ptr(p, "1");
  dsvec_add_ptr(p, "2");
  dsvec_add_ptr(p, "3");
  dsvec_add_ptr(p, "4");
  if (!dsvec_streq(p, dsvec_strlist(NULL, "1,2,3,4")))
	return(-1);
  pc = dsvec_copy(NULL, p);
  if (!dsvec_streq(pc, p))
	return(-1);

  dsvec_add_ptr(s, "5");
  dsvec_add_ptr(s, "6");
  dsvec_add_ptr(s, "7");
  if (!dsvec_streq(s, dsvec_strlist(NULL, "5,6,7")))
	return(-1);
  sc = dsvec_copy(NULL, s);
  if (!dsvec_streq(sc, s))
	return(-1);

  fprintf(out, "Vec1 is: ");
  selftest_show(out, p);
  fprintf(out, "\n");

  fprintf(out, "Vec2 is: ");
  selftest_show(out, s);
  fprintf(out, "\n");

  if (selftest(out, SELFTEST_APPEND, pc, 0, 0, sc, "Append Vec2 to Vec1",
			   "1,2,3,4,5,6,7") == -1)
	return(-1);

  if (selftest(out, SELFTEST_INSERT, pc, 0, 0, sc, "Prepend Vec2 to Vec1",
			   "5,6,7,1,2,3,4") == -1)
	return(-1);

  if (selftest(out, SELFTEST_INSERT, pc, 1, 0, sc,
			   "Insert Vec2 after first element of Vec1",
			   "1,5,6,7,2,3,4") == -1)
	return(-1);

  if (selftest(out, SELFTEST_DELETE, pc, 0, 1, NULL,
			   "Delete the first element of Vec1", "2,3,4") == -1)
	return(-1);

  if (selftest(out, SELFTEST_DELETE, pc, 1, 1, NULL,
			   "Delete the second element of Vec1", "1,3,4") == -1)
	return(-1);
 
  if (selftest(out, SELFTEST_DELETE, pc, dsvec_len(pc) - 1, 1, NULL,
			   "Delete the last element of Vec1", "1,2,3") == -1)
	return(-1);

  if (selftest(out, SELFTEST_DELETE, pc, 1, dsvec_len(pc) - 2, NULL,
			   "Delete middle elements of Vec1", "1,4") == -1)
	return(-1);

  if (selftest(out, SELFTEST_REPLACE, pc, 0, dsvec_len(pc), sc,
			   "Replace Vec1 with Vec2", "5,6,7") == -1)
	return(-1);

  if (selftest(out, SELFTEST_REPLACE, pc, 1, dsvec_len(pc) - 2, sc,
			   "Replace middle of Vec1 with Vec2", "1,5,6,7,4") == -1)
	return(-1);

  if (selftest(out, SELFTEST_SLICE, pc, 1, dsvec_len(pc) - 2, NULL,
			   "Extract middle of Vec1", "2,3") == -1)
	return(-1);

  fprintf(out, "dsvec_rotate: ");
  dsv = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(dsv, "a");
  dsvec_add_ptr(dsv, "b");
  dsvec_add_ptr(dsv, "c");
  dsvec_add_ptr(dsv, "d");
  dsvec_rotate(dsv, 2);
  if (!dsvec_streq(dsv, dsvec_strlist(NULL, "c,d,a,b"))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  dsvec_add_ptr(dsv, "e");
  dsvec_rotate(dsv, -3);
  if (!dsvec_streq(dsv, dsvec_strlist(NULL, "b,e,c,d,a"))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "dsvec_shift: ");
  dsv = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(dsv, "a");
  dsvec_add_ptr(dsv, "b");
  dsvec_add_ptr(dsv, "c");
  dsvec_add_ptr(dsv, "d");
  dsvec_shift(dsv, 2);
  if (!dsvec_streq(dsv, dsvec_strlist(NULL, "c,d"))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  dsvec_add_ptr(dsv, "e");
  dsvec_shift(dsv, 1);
  if (!dsvec_streq(dsv, dsvec_strlist(NULL, "d,e"))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "dsvec_range: ");
  if ((dsv = dsvec_range(NULL, p, "0-3", NULL)) == NULL
	  || !dsvec_streq(dsv, dsvec_strlist(NULL, "1,2,3,4"))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_range: ");
  if ((x = ds_range(NULL, "hello, world.", "1,2,6,7-11", NULL)) == NULL
	  || !streq(ds_buf(x), "el world")) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_mkargv: ");
  dsv = ds_mkargv(NULL, "a b c d e", NULL);
  ds_mkargv(dsv, "f g h", NULL);
  ds_mkargv_add(dsv, "i");
  ds_mkargv_add(dsv, "j");
  a[0] = "k";
  a[1] = "l";
  ds_mkargv_addv(dsv, 2, a);
  if (!dsvec_strneq(dsv, dsvec_strlist(NULL, "a,b,c,d,e,f,g,h,i,j,k,l"), 12)) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "dsvec_csv: ");
  if (dsvec_csv_test(out) == -1) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_sprintf: ");
  x = ds_alloc();
  n = ds_sprintf(x, 0, "012345");
  n += ds_sprintf(x, n, "6789abcdef");
  if (!streq(ds_buf(x), "0123456789abcdef")) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_appendc: ");
  ds_reset(x);
  ds_appendc(x, (int) 'a');
  ds_appendc(x, (int) 'b');
  ds_appendc(x, (int) 'c');
  ds_appendc(x, (int) 'd');
  ds_appendc(x, (int) '\01');
  ds_appendc(x, (int) '\02');
  ds_appendc(x, (int) '\0');
  if (!streq(ds_buf(x), "abcd\1\2")) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_asprintf: ");
  ds_reset(x);
  y = ds_alloc();
  ds_asprintf(x, "%s%s", "0123", "45678");
  ds_asprintf(x, "9a%s", "bcdef");
  ds_sprintf(y, 0, "0123456789abcdef");
  if (!streq(ds_buf(x), "0123456789abcdef") || !streq(ds_buf(x), ds_buf(y))) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "ds_xsprintf: ");
  ptr = ds_xprintf("%d%d%d%d%s", 0, 1, 2, 3456789, "abcdef\n");
  if (!streq(ptr, "0123456789abcdef\n")) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "ok\n");

  fprintf(out, "Search and completion tests begin\n");
  if (search_test(out) == -1) {
	fprintf(out, "failed!\n");
	return(-1);
  }
  fprintf(out, "Search and completion tests: ok\n");

  return(0);
}

#ifdef NOTDEF
/*
 * Convert DSV into a traditional argv-style vector, terminated by a
 * NULL entry; set ARGVP to point to it and ARGCP to the number of vector
 * entries (not counting the NULL).
 * Return the number of vector entries, or -1 if an error occurs.
 */
int
dsvec_mkargv(Dsvec *dsv, char ***argvp, int *argcp)
{
  int argc, i, n;
  char **argv;

  if (dsv == NULL)
	return(-1);

  n = dsvec_len(dsv);
  argv = *argvp = (char **) malloc(sizeof(char *) * (n + 1));
  argc = *argcp = n;

  for (i = 0; i < n; i++)
	argv[i] = (char *) dsvec_ptr_index(dsv, i);

  argv[i] = NULL;

  return(n);
}
#endif

#ifdef PROG
int
main(int argc, char **argv)
{
  char *p;
  Ds ds, *x;
  Dsio *in;
  FILE *fp;

  fprintf(stderr, "Testing dynamic strings...\n");
  if (dsvec_selftest(stderr) == -1) {
	fprintf(stderr, "Dynamic string self-test failed!\n");
	exit(1);
  }

  if (argc == 2 && streq(argv[1], "-batch")) {
	fprintf(stderr, "All dynamic string tests succeeded!\n");
	exit(0);
  }

  /* The following are all interactive tests */

  fprintf(stderr, "Interactive testing begins...\n");
  x = ds_init(NULL);
  p = ds_prompt(x, "Enter any filename to read: ", DS_PROMPT_ECHO);
  if ((fp = fopen(p, "r")) == NULL) {
	fprintf(stderr, "Cannot read \"%s\"\n", p);
	exit(1);
  }
  ds_init(&ds);
  dsio_set(&ds, fp, NULL, 0, 0);
  if (dsio_load(&ds) == -1) {
	fprintf(stderr, "Cannot load \"%s\"\n", p);
	exit(1);
  }
  fclose(fp);
  printf("Read %u bytes\n", (unsigned int) ds_len(&ds));

  fprintf(stderr, "Type some stuff now - will read to EOF...\n");
  ds_init(&ds);
  if ((in = dsio_set(&ds, stdin, NULL, 0, 0)) == NULL) {
	fprintf(stderr, "dsio_set() failed\n");
	exit(1);
  }
  if ((p = dsio_load_str(&ds)) == NULL) {
	fprintf(stderr, "dsio_load_str() failed\n");
	exit(1);
  }
  printf("Got:\n\"%s\"\n", p);

  x = ds_alloc();
  if ((p = ds_prompt(x, "Prompt: ", DS_PROMPT_NOECHO)) == NULL)
	fprintf(stderr, "error: %s\n", ds_errmsg);
  else
	fprintf(stderr, "Read: %s\n", p);
  ds_free(x);

  ds_reset(x);
  if ((p = ds_gets(x, stdin)) != NULL)
	printf("%s\n", p);

  fprintf(stderr, "Interactive testing ends.\n");
  fprintf(stderr, "All dynamic string tests succeeded!\n");

  exit(0);
}
#endif
