/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2010-2025 Free Software Foundation, Inc.

   GNU Mailutils is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Mailutils is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */

#include <config.h>
#include <mailutils/mailutils.h>
#include <mailutils/dbm.h>
#include <fnmatch.h>
#include <regex.h>
#include <sys/stat.h>
#include <grp.h>
#include <sysexits.h>
#include "mu.h"

static char dbm_doc[] = N_("DBM management tool\n"
"Valid COMMANDs are:\n"
"\n"
"  create or load - create the database\n"
"  dump           - dump the database\n"
"  list           - list contents of the database\n"
"  delete         - delete specified keys from the database\n"
"  add            - add records to the database\n"
"  replace        - add records replacing ones with matching keys\n");

char dbm_docstring[] = N_("DBM management tool");
static char dbm_args_doc[] = N_("COMMAND FILE [KEY...]");

enum mode
  {
    mode_list,
    mode_dump,
    mode_create,
    mode_delete,
    mode_add,
    mode_replace,
  };

enum key_type
  {
    key_literal,
    key_glob,
    key_regex
  };

static enum mode mode;
static char *db_name;
static char *input_file;
static int file_mode = 0600;
static struct mu_auth_data *auth;
static uid_t owner_uid;
static gid_t owner_gid;
static char *owner_user;
static char *owner_group;
static int copy_permissions;

#define META_FILE_MODE 0x0001
#define META_UID       0x0002
#define META_GID       0x0004
#define META_USER      0x0008
#define META_GROUP     0x0010
#define META_AUTH      0x0020

static int known_meta_data;

static enum key_type key_type = key_literal;
static int case_sensitive = 1;
static int include_zero = -1;

static int suppress_header = -1;


static void
init_datum (struct mu_dbm_datum *key, char *str)
{
  memset (key, 0, sizeof *key);
  key->mu_dptr = str;
  key->mu_dsize = strlen (str) + !!include_zero;
}

static mu_dbm_file_t
open_db_file (int mode)
{
  int rc;
  mu_dbm_file_t db;

  if (!db_name)
    {
      mu_error (_("database name not given"));
      exit (EX_USAGE);
    }
  
  rc = mu_dbm_create (db_name, &db, 0);
  if (rc)
    {
      mu_diag_output (MU_DIAG_ERROR, _("unable to create database %s: %s"),
		      db_name, mu_strerror (rc));
      exit (EX_SOFTWARE);
    }

  rc = mu_dbm_open (db, mode, file_mode);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_open", db_name, rc);
      exit (EX_SOFTWARE);
    }

  return db;
}

static void
set_db_ownership (mu_dbm_file_t db)
{
  int rc, dirfd, pagfd;
  struct stat st;
  
  rc = mu_dbm_get_fd (db, &pagfd, &dirfd);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_get_fd", db_name, rc);
      exit (EX_SOFTWARE);
    }
  
  if (known_meta_data & META_USER)
    {
      struct mu_auth_data *ap;
      
      ap = mu_get_auth_by_name (owner_user);
      if (!ap)
	{
	  if (!(known_meta_data & META_UID))
	    {
	      mu_error (_("no such user: %s"), owner_user);
	      exit (EX_NOUSER);
	    }
	}
      else
	{
	  owner_uid = ap->uid;
	  known_meta_data |= META_UID;
	  mu_auth_data_free (ap);
	}
    }
  
  if (known_meta_data & META_GROUP)
    {
      struct group *gr = getgrnam (owner_group);
      if (!gr)
	{
	  if (!(known_meta_data & META_GID))
	    {
	      mu_error (_("no such group: %s"), owner_group);
	      exit (EX_DATAERR);
	    }
	}
      else
	{
	  owner_gid = gr->gr_gid;
	  known_meta_data |= META_GID;
	}
    }
  
  if (fstat (dirfd, &st))
    {
      mu_diag_funcall (MU_DIAG_ERROR, "fstat", db_name, errno);
      exit (EX_SOFTWARE);
    }
  
  if (known_meta_data & META_FILE_MODE)
    {
      if (fchmod (pagfd, file_mode) ||
	  (pagfd != dirfd && fchmod (dirfd, file_mode)))
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "fchmod", db_name, errno);
	  exit (EX_SOFTWARE);
	}
    }	      
  
  if (known_meta_data & (META_UID|META_GID))
    {
      if (!(known_meta_data & META_UID))
	owner_uid = st.st_uid;
      else if (!(known_meta_data & META_GID))
	owner_gid = st.st_gid;
      
      if (fchown (pagfd, owner_uid, owner_gid) ||
	  (pagfd != dirfd && fchown (dirfd, owner_uid, owner_gid)))
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "fchown", db_name, errno);
	  exit (EX_SOFTWARE);
	}
    }
}


#define IOBUF_REREAD 1
#define IOBUF_EOF    2

struct iobuf
{
  mu_stream_t stream;
  char *buffer;
  size_t bufsize;
  size_t length;
  int flag;            /* 0 or one of the IOBUF_ constants above. */
};

static int is_dbm_directive (struct iobuf *input);
static int is_len_directive (struct iobuf *input);
static int is_ignored_directive (const char *name);
static int set_directive (char *val);
static void print_header (const char *name, mu_stream_t str);

#define input_eof(iob) ((iob)->flag == IOBUF_EOF)

static int
input_getline (struct iobuf *inp)
{
  int rc;
  size_t len;

  switch (inp->flag)
    {
    case 0:
      break;

    case IOBUF_REREAD:
      inp->flag = 0;
      return 0;

    case IOBUF_EOF:
      inp->length = 0;
      return 0;
    }
  
  while (1)
    {
      rc = mu_stream_getline (inp->stream, &inp->buffer, &inp->bufsize, &len);
      if (rc)
	return rc;
      if (len == 0)
	{
	  inp->flag = IOBUF_EOF;
	  inp->length = 0;
	  break;
	}
      mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
		       MU_IOCTL_LOGSTREAM_ADVANCE_LOCUS_LINE, NULL);
      inp->length = mu_rtrim_class (inp->buffer, MU_CTYPE_ENDLN);
      if (inp->buffer[0] == '#' && !is_dbm_directive (inp))
	{
	  struct mu_locus_range loc;
	  mu_stream_ioctl (mu_strerr,
			   MU_IOCTL_LOGSTREAM,
			   MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE,
			   &loc);
	  loc.beg.mu_line = strtoul (inp->buffer + 1, NULL, 10);
	  mu_stream_ioctl (mu_strerr,
			   MU_IOCTL_LOGSTREAM,
			   MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE,
			   &loc);
	  mu_locus_range_deinit (&loc);
	  continue;
	}
      break;
    }
  return 0;
}

static void
input_ungetline (struct iobuf *inp)
{
  if (inp->flag == 0)
    inp->flag = IOBUF_REREAD;
}

static size_t
input_length (struct iobuf *inp)
{
  return inp->length;
}

struct xfer_format
{
  const char *name;
  void (*init) (struct xfer_format *fmt, const char *version,
		struct iobuf *iop, int wr);
  int (*reader) (struct iobuf *, void *data, struct mu_dbm_datum *key,
		 struct mu_dbm_datum *content);
  int (*writer) (struct iobuf *, void *data, struct mu_dbm_datum const *key,
		 struct mu_dbm_datum const *content);
  void *data;
};

static int ascii_reader (struct iobuf *, void *data,
			 struct mu_dbm_datum *key,
			 struct mu_dbm_datum *content);
static int ascii_writer (struct iobuf *, void *data,
			 struct mu_dbm_datum const *key,
			 struct mu_dbm_datum const *content);

static int C_reader (struct iobuf *, void *data,
		     struct mu_dbm_datum *key,
		     struct mu_dbm_datum *content);
static int C_writer (struct iobuf *, void *data,
		     struct mu_dbm_datum const *key,
		     struct mu_dbm_datum const *content);

static void newfmt_init (struct xfer_format *fmt,
			 const char *version, struct iobuf *iop, int wr);

static int newfmt_reader (struct iobuf *, void *data,
			  struct mu_dbm_datum *key,
			  struct mu_dbm_datum *content);
static int newfmt_writer (struct iobuf *, void *data,
			  struct mu_dbm_datum const *key,
			  struct mu_dbm_datum const *content);

static struct xfer_format format_tab[] = {
  { "C",   NULL, C_reader,     C_writer, NULL },
  { "0.0", NULL, ascii_reader, ascii_writer, NULL },
  { "1.0", newfmt_init, newfmt_reader, newfmt_writer, NULL },
  { NULL }
};
						     
#define DEFAULT_LIST_FORMAT (&format_tab[1])
#define DEFAULT_DUMP_FORMAT (&format_tab[2])
#define DEFAULT_LOAD_FORMAT (&format_tab[1])

static struct xfer_format *format;
static const char *dump_format_version;

						     
static int
read_data (struct iobuf *inp, struct mu_dbm_datum *key,
	   struct mu_dbm_datum *content)
{
  return format->reader (inp, format->data, key, content);
}

static int
write_data (struct iobuf *iop, struct mu_dbm_datum const *key,
	    struct mu_dbm_datum const *content)
{
  return format->writer (iop, format->data, key, content);
}     
						     
static int
select_format (const char *version)
{
  struct xfer_format *fmt;

  dump_format_version = version;
  for (fmt = format_tab; fmt->name; fmt++)
    if (strcmp (fmt->name, version) == 0)
      {
	format = fmt;
	return 0;
      }
  mu_error (_("unsupported format version: %s"), version);
  return 1;
}

static void
init_format (int wr, struct iobuf *iop)
{
  if (format->init)
    format->init (format,
		  dump_format_version ? dump_format_version : format->name,
		  iop, wr);
}


static int
ascii_writer (struct iobuf *iop, void *data,
	      struct mu_dbm_datum const *key,
	      struct mu_dbm_datum const *content)
{
  size_t len;

  if (!data)
    {
      if (!suppress_header && include_zero == 1 &&
	  !is_ignored_directive ("null"))
	mu_stream_printf (iop->stream, "#:null\n");
      data = ascii_writer;
    }
  
  len = key->mu_dsize;
  if (key->mu_dptr[len - 1] == 0)
    len--;
  mu_stream_write (iop->stream, key->mu_dptr, len, NULL);
  mu_stream_write (iop->stream, "\t", 1, NULL);

  len = content->mu_dsize;
  if (content->mu_dptr[len - 1] == 0)
    len--;
  mu_stream_write (iop->stream, content->mu_dptr, len, NULL);
  mu_stream_write (iop->stream, "\n", 1, NULL);
  return 0;
}

static int
ascii_reader (struct iobuf *inp, void *data MU_ARG_UNUSED,
	      struct mu_dbm_datum *key,
	      struct mu_dbm_datum *content)
{
  char *kstr, *val;
  int rc;
  
  rc = input_getline (inp);
  if (rc)
    {
      mu_error ("%s", mu_strerror (rc));
      return -1;
    }
  if (input_eof (inp))
    return 1;
  if (input_length (inp) == 0)
    {
      key->mu_dsize = 0;
      return 0;
    }
  kstr = mu_str_stripws (inp->buffer);
  val = mu_str_skip_class_comp (kstr, MU_CTYPE_SPACE);
  *val++ = 0;
  val = mu_str_skip_class (val, MU_CTYPE_SPACE);
  if (*val == 0)
    {
      mu_error (_("malformed line"));
      key->mu_dsize = 0;
    }
  else
    {
      init_datum (key, kstr);
      init_datum (content, val);
    }
  return 0;
}  

void
write_quoted_string (mu_stream_t stream, char *str, size_t len)
{
  for (; len; str++, len--)
    {
      if (*str == '"')
	{
	  mu_stream_write (stream, "\\", 1, NULL);
	  mu_stream_write (stream, str, 1, NULL);
	}
      else if (*str != '\t' && *str != '\\' && mu_isprint (*str))
	mu_stream_write (stream, str, 1, NULL);
      else
	{
	  int c = mu_wordsplit_c_quote_char (*str);

	  mu_stream_write (stream, "\\", 1, NULL);
	  if (c)
	    mu_stream_write (stream, str, 1, NULL);
	  else
	    mu_stream_printf (stream, "%03o", *(unsigned char *) str);
	}
    }
}

static int
C_writer (struct iobuf *iop, void *data MU_ARG_UNUSED,
	  struct mu_dbm_datum const *key,
	  struct mu_dbm_datum const *content)
{
  write_quoted_string (iop->stream, key->mu_dptr, key->mu_dsize);
  mu_stream_write (iop->stream, "\n", 1, NULL);
  write_quoted_string (iop->stream, content->mu_dptr, content->mu_dsize);
  mu_stream_write (iop->stream, "\n\n", 2, NULL);
  return 0;
}

#define ISODIGIT(c) ((c) >= '0' && c < '8')
#define OCTVAL(c) ((c) - '0')

static int
C_read_datum (struct iobuf *inp, struct mu_dbm_datum *datum)
{
  size_t i = 0;
  char *base, *ptr;
  size_t length;
  int rc;
  
  free (datum->mu_dptr);
  memset (datum, 0, sizeof (*datum));

  rc = input_getline (inp);
  if (rc)
    {
      mu_error ("%s", mu_strerror (rc));
      return -1;
    }
  if (input_eof (inp))
    return 1;
  if ((length = input_length (inp)) == 0)
    {
      mu_error (_("empty line"));
      return -1;
    }

  memset (datum, 0, sizeof (*datum));
  datum->mu_dptr = ptr = mu_alloc (length);
  base = inp->buffer;
  for (i = 0; i < length; ptr++)
    {
      if (base[i] == '\\')
	{
	  if (++i >= length)
	    {
	      mu_error (_("unfinished string"));
	      return -1;
	    }
	  else if (ISODIGIT (base[i]))
	    {
	      unsigned c;

	      if (i + 3 > length)
		{
		  mu_error (_("unfinished string"));
		  return -1;
		}
	      c = OCTVAL (base[i]);
	      c <<= 3;
	      c |= OCTVAL (base[i + 1]);
	      c <<= 3;
	      c |= OCTVAL (base[i + 2]);
	      *ptr = c;
	      i += 3;
	    }
	  else
	    {
	      int c = mu_wordsplit_c_unquote_char (base[i]);
	      if (c == -1)
		{
		  mu_error (_("invalid escape sequence (\\%c)"), base[i]);
		  return -1;
		}
	      *ptr = c;
	    }
	}
      else
	*ptr = base[i++];
    }
  datum->mu_dsize = ptr - datum->mu_dptr;
  return 0;
}

static int
C_reader (struct iobuf *inp, void *data MU_ARG_UNUSED,
	  struct mu_dbm_datum *key,
	  struct mu_dbm_datum *content)
{
  int rc;

  rc = C_read_datum (inp, key);
  if (rc < 0)
    exit (EX_DATAERR);
  else if (rc == 1)
    return 1;
  
  if (C_read_datum (inp, content))
    exit (EX_DATAERR);

  rc = input_getline (inp);
  if (rc)
    {
      mu_error ("%s", mu_strerror (rc));
      return -1;
    }

  if (input_length (inp) != 0)
    {
      mu_error (_("unrecognized line"));
      return -1;
    }

  return 0;
}  

static void
newfmt_init (struct xfer_format *fmt, const char *version,
	     struct iobuf *iop, int wr)
{
  int rc;

  if (!wr)
    {
      mu_stream_t flt;
	
      rc = mu_linelen_filter_create (&flt, iop->stream, 76, MU_STREAM_WRITE);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_linelen_filter_create",
			   NULL, rc);
	  exit (EX_SOFTWARE);
	}
      iop->stream = flt;
    }
  else
    {
      mu_opool_t pool;
      rc = mu_opool_create (&pool, MU_OPOOL_DEFAULT);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_opool_create",
			   NULL, rc);
	  exit (EX_SOFTWARE);
	}
      fmt->data = pool;
    }      
}

static int
newfmt_read_datum (struct iobuf *iop, mu_opool_t pool,
		   struct mu_dbm_datum *datum)
{
  int rc;
  unsigned char *ptr, *out;
  size_t len;

  free (datum->mu_dptr);
  memset (datum, 0, sizeof (*datum));
	  
  rc = input_getline (iop);
  if (rc)
    {
      mu_error ("%s", mu_strerror (rc));
      exit (EX_IOERR);
    }
  if (input_eof (iop))
    return 1;
  
  if (!is_len_directive (iop))
    {
      mu_error (_("unrecognized input line"));
      exit (EX_DATAERR);
    }
  else
    {
      char *p;
      unsigned long n;
  
      errno = 0;
      n = strtoul (iop->buffer + 6, &p, 10);
      if (*p || errno)
	{
	  mu_error (_("invalid length"));
	  exit (EX_DATAERR);
	}
      datum->mu_dsize = n;
    }

  mu_opool_clear (pool);
  while (1)
    {
      rc = input_getline (iop);
      if (rc)
	{
	  mu_error ("%s", mu_strerror (rc));
	  exit (EX_IOERR);
	}
      if (input_eof (iop))
	break;
      if (is_len_directive (iop))
	{
	  input_ungetline (iop);
	  break;
	}
      mu_opool_append (pool, iop->buffer, iop->length);
    }

  ptr = mu_opool_finish (pool, &len);
  
  rc = mu_base64_decode (ptr, len, &out, &len);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_base64_decode", NULL, rc);
      exit (EX_SOFTWARE);
    }

  datum->mu_dptr = (void*) out;
  return 0;
}

static int
newfmt_reader (struct iobuf *input, void *data,
	       struct mu_dbm_datum *key,
	       struct mu_dbm_datum *content)
{
  mu_opool_t pool = data;
  if (newfmt_read_datum (input, pool, key))
    return 1;
  if (newfmt_read_datum (input, pool, content))
    return 1;
  return 0;
}

static void
newfmt_write_datum (struct iobuf *iop, struct mu_dbm_datum const *datum)
{
  int rc;
  
  mu_stream_printf (iop->stream, "#:len=%lu\n",
		    (unsigned long) datum->mu_dsize);

  rc = mu_base64_encode ((unsigned char*)datum->mu_dptr, datum->mu_dsize,
			 (unsigned char **)&iop->buffer, &iop->bufsize);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_base64_encode", NULL, rc);
      exit (EX_SOFTWARE);
    }
  mu_stream_write (iop->stream, iop->buffer, iop->bufsize, NULL);
  free (iop->buffer); /* FIXME */
  mu_stream_write (iop->stream, "\n", 1, NULL);
}

static int
newfmt_writer (struct iobuf *iop, void *data,
	       struct mu_dbm_datum const *key,
	       struct mu_dbm_datum const *content)
{
  newfmt_write_datum (iop, key);
  newfmt_write_datum (iop, content);
  return 0;
}


struct print_data
{
  mu_dbm_file_t db;
  struct iobuf iob;
};
  
static int
print_action (struct mu_dbm_datum const *key, void *data)
{
  int rc;
  struct mu_dbm_datum contents;
  struct print_data *pd = data;
  
  if (!suppress_header)
    {
      int fd;
      struct stat st;
      const char *name, *p;
      rc = mu_dbm_get_fd (pd->db, &fd, NULL);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_get_fd", db_name, rc);
	  exit (EX_SOFTWARE);
	}
      if (fstat (fd, &st))
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "fstat", db_name, errno);
	  exit (EX_UNAVAILABLE);
	}
  
      if (!(known_meta_data & META_UID))
	{
	  known_meta_data |= META_UID;
	  owner_uid = st.st_uid;
	}
      if (!(known_meta_data & META_GID))
	{
	  known_meta_data |= META_GID;
	  owner_gid = st.st_gid;
	}
      if (!(known_meta_data & META_FILE_MODE))
	{
	  known_meta_data |= META_FILE_MODE;
	  file_mode = st.st_mode & 0777;
	}
      
      if (!(known_meta_data & META_USER))
	{
	  struct mu_auth_data *ap = mu_get_auth_by_uid (owner_uid);
	  if (ap)
	    {
	      owner_user = mu_strdup (ap->name);
	      known_meta_data |= META_USER;
	      mu_auth_data_free (ap);
	    }
	}

      if (!(known_meta_data & META_GROUP))
	{
	  struct group *gr = getgrgid (owner_gid);
	  if (gr)
	    {
	      owner_group = mu_strdup (gr->gr_name);
	      known_meta_data |= META_GROUP;
	    }
	}
      
      mu_dbm_get_name (pd->db, &name);
      p = strrchr (name, '/');
      if (p)
	p++;
      else
	p = name;
      print_header (p, mu_strout);
    }
  
  memset (&contents, 0, sizeof contents);
  rc = mu_dbm_fetch (pd->db, key, &contents);
  if (rc)
    {
      mu_error (_("database fetch error: %s"), mu_dbm_strerror (pd->db));
      exit (EX_UNAVAILABLE);
    }
  rc = write_data (&pd->iob, key, &contents);
  mu_dbm_datum_free (&contents);
  suppress_header = 1;
  return rc;
}

static void
iterate_database (mu_dbm_file_t db,
		  int (*matcher) (const char *, void *), void *matcher_data,
		  int (*action) (struct mu_dbm_datum const *, void *),
		  void *action_data)
{
  int rc;
  struct mu_dbm_datum key;
  char *buf = NULL;
  size_t bufsize = 0;

  memset (&key, 0, sizeof key);
  for (rc = mu_dbm_firstkey (db, &key); rc == 0;
       rc = mu_dbm_nextkey (db, &key))
    {
      if (include_zero == -1)
	include_zero = key.mu_dptr[key.mu_dsize-1] == 0;

      if (matcher)
	{
	  if (key.mu_dsize + 1 > bufsize)
	    {
	      bufsize = key.mu_dsize + 1;
	      buf = mu_realloc (buf, bufsize);
	    }
	  memcpy (buf, key.mu_dptr, key.mu_dsize);
	  buf[key.mu_dsize] = 0;
	  if (!matcher (buf, matcher_data))
	    continue;
	}
      if (action (&key, action_data))
	break;
    }
  free (buf);
}

static int
match_literal (const char *str, void *data)
{
  char **argv = data;

  for (; *argv; argv++)
    {
      if ((case_sensitive ? strcmp : strcasecmp) (str, *argv) == 0)
	return 1;
    }
  return 0;
}

#ifndef FNM_CASEFOLD
# define FNM_CASEFOLD 0
#endif

static int
match_glob (const char *str, void *data)
{
  char **argv = data;
  
  for (; *argv; argv++)
    {
      if (fnmatch (*argv, str, case_sensitive ? FNM_CASEFOLD : 0) == 0)
	return 1;
    }
  return 0;
}

struct regmatch
{
  int regc;
  regex_t *regs;
};
  
static int
match_regex (const char *str, void *data)
{
  struct regmatch *match = data;
  int i;
  
  for (i = 0; i < match->regc; i++)
    {
      if (regexec (&match->regs[i], str, 0, NULL, 0) == 0)
	return 1;
    }
  return 0;
}

static void
compile_regexes (int argc, char **argv, struct regmatch *match)
{
  regex_t *regs = mu_calloc (argc, sizeof (regs[0]));
  int i;
  int cflags = (case_sensitive ? 0: REG_ICASE) | REG_EXTENDED | REG_NOSUB;
  int errors = 0;
  
  for (i = 0; i < argc; i++)
    {
      int rc = regcomp (&regs[i], argv[i], cflags);
      if (rc)
	{
	  char errbuf[512];
	  regerror (rc, &regs[i], errbuf, sizeof (errbuf));
	  mu_error (_("error compiling %s: %s"), mu_quotename (argv[i]),
		    errbuf);
	  errors++;
	}
    }
  if (errors)
    exit (EX_USAGE);
  match->regc = argc;
  match->regs = regs;
}

static void
free_regexes (struct regmatch *match)
{
  int i;
  for (i = 0; i < match->regc; i++)
    regfree (&match->regs[i]);
  free (match->regs);
}

static void
list_database (int argc, char **argv)
{
  mu_dbm_file_t db = open_db_file (MU_STREAM_READ);
  struct print_data pdata;
  
  memset (&pdata, 0, sizeof pdata);
  pdata.db = db;
  pdata.iob.stream = mu_strout;

  init_format (0, &pdata.iob);
  
  if (argc == 0)
    iterate_database (db, NULL, NULL, print_action, &pdata);
  else
    {
      switch (key_type)
	{
	case key_literal:
	  iterate_database (db, match_literal, argv, print_action, &pdata);
	  break;

	case key_glob:
	  iterate_database (db, match_glob, argv, print_action, &pdata);
	  break;

	case key_regex:
	  {
	    struct regmatch m;
	    compile_regexes (argc, argv, &m);
	    iterate_database (db, match_regex, &m, print_action, &pdata);
	    free_regexes (&m);
	  }
	}
    }
  mu_dbm_destroy (&db);
  mu_stream_close (pdata.iob.stream);
}

static int
is_dbm_directive (struct iobuf *input)
{
  return input->length > 2 &&
         input->buffer[0] == '#' &&
         input->buffer[1] == ':';
}

static int
is_len_directive (struct iobuf *input)
{
  return input->length > 6 && memcmp (input->buffer, "#:len=", 6) == 0;
}

/*
  #:version=1.0                       # Version number
  #:filename=S                        # Original database name (basename)
  #:uid=N,user=S,gid=N,group=S,mode=N # File meta info
  #:null[=0|1]                        # Null termination (v.0.0 only)
  #:len=N                             # record length (v.1.0)
*/

static int
_set_version (const char *val)
{
  if (select_format (val))
    exit (EX_DATAERR);
  return 0;
}

static int
_set_file (const char *val)
{
  if (!db_name)
    db_name = mu_strdup (val);
  return 0;
}

static int
_set_null (const char *val)
{
  if (!val)
    include_zero = 1;
  else if (val[0] == '0' && val[1] == 0)
    include_zero = 0;
  else if (val[0] == '1' && val[1] == 0)
    include_zero = 1;
  else
    {
      mu_error (_("bad value for 'null' directive: %s"), val);
      exit (EX_DATAERR);
    }
  return 0;
}

static int
_set_uid (const char *val)
{
  char *p;
  unsigned long n;

  if (known_meta_data & META_UID)
    return 0;
  errno = 0;
  n = strtoul (val, &p, 8);
  if (*p || errno)
    {
      mu_error (_("invalid UID"));
      exit (EX_NOUSER);
    }
  owner_uid = n;
  known_meta_data |= META_UID;
  return 0;
}

static int
_set_gid (const char *val)
{
  char *p;
  unsigned long n;
  
  if (known_meta_data & META_GID)
    return 0;
  errno = 0;
  n = strtoul (val, &p, 8);
  if (*p || errno)
    {
      mu_error (_("invalid GID"));
      exit (EX_DATAERR);
    }
  owner_gid = n;
  known_meta_data |= META_GID;
  return 0;
}

static int
_set_user (const char *val)
{
  if (known_meta_data & META_USER)
    return 0;
  free (owner_user);
  owner_user = mu_strdup (val);
  known_meta_data |= META_USER;
  return 0;
}

static int
_set_group (const char *val)
{
  if (known_meta_data & META_GROUP)
    return 0;
  free (owner_group);
  owner_group = mu_strdup (val);
  known_meta_data |= META_GROUP;
  return 0;
}

static int
_set_mode (const char *val)
{
  char *p;
  unsigned long n;
  
  if (known_meta_data & META_FILE_MODE)
    return 0;
  errno = 0;
  n = strtoul (val, &p, 8);
  if (*p || errno || n > 0777)
    {
      mu_error (_("invalid file mode"));
      exit (EX_DATAERR);
    }
  file_mode = n;
  known_meta_data |= META_FILE_MODE;
  return 0;
}

struct dbm_directive
{
  const char *name;
  size_t len;
  int (*set) (const char *val);
  int flags;
};

#define DF_VALUE   0x01  /* directive requires a value */
#define DF_HEADER  0x02  /* can appear only in file header */
#define DF_PROTECT 0x04  /* directive cannot be ignored */
#define DF_META    0x08  /* directive keeps file meta-information */
#define DF_SEEN    0x10  /* directive was already seen */
#define DF_IGNORE  0x20  /* ignore this directive */

static struct dbm_directive dbm_directive_table[] = {
#define S(s) #s, sizeof #s - 1
  { S(version), _set_version, DF_HEADER|DF_VALUE|DF_PROTECT },
  { S(file),    _set_file,    DF_HEADER|DF_VALUE },
  { S(uid),     _set_uid,     DF_HEADER|DF_META|DF_VALUE },
  { S(user),    _set_user,    DF_HEADER|DF_META|DF_VALUE },
  { S(gid),     _set_gid,     DF_HEADER|DF_META|DF_VALUE },
  { S(group),   _set_group,   DF_HEADER|DF_META|DF_VALUE },
  { S(mode),    _set_mode,    DF_HEADER|DF_META|DF_VALUE },
  { S(null),    _set_null,    DF_HEADER|DF_VALUE },
  { S(null),    _set_null,    DF_HEADER },
#undef S
  { NULL }
};

static void
ignore_directives (const char *arg)
{
  struct mu_wordsplit ws;
  size_t i;
  
  ws.ws_delim = ",";
  if (mu_wordsplit (arg, &ws,
		    MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | MU_WRDSF_DELIM))
    {
      mu_error (_("cannot split input line: %s"), mu_wordsplit_strerror (&ws));
      exit (EX_SOFTWARE);
    }

  for (i = 0; i < ws.ws_wordc; i++)
    {
      char *arg = ws.ws_wordv[i];
      size_t len = strlen (arg);
      struct dbm_directive *p;
      int found = 0;
      
      for (p = dbm_directive_table; p->name; p++)
	{
	  if (p->len == len && memcmp (p->name, arg, len) == 0)
	    {
	      if (p->flags & DF_PROTECT)
		mu_error (_("directive %s cannot be ignored"), p->name);
	      else
		p->flags |= DF_IGNORE;
	      found = 1;
	    }
	}
      if (!found)
	mu_error (_("unknown directive: %s"), arg);
    }
  mu_wordsplit_free (&ws);
}

static int
is_ignored_directive (const char *arg)
{
  size_t len = strlen (arg);
  struct dbm_directive *p;
      
  for (p = dbm_directive_table; p->name; p++)
    {
      if (p->len == len && memcmp (p->name, arg, len) == 0)
	return p->flags & DF_IGNORE;
    }
  return 0;
}

static void
ignore_flagged_directives (int flag)
{
  struct dbm_directive *p;
  
  for (p = dbm_directive_table; p->name; p++)
    if (!(p->flags & DF_PROTECT) && (p->flags & flag))
      p->flags |= DF_IGNORE;
}

static int
set_directive (char *val)
{
  size_t len;
  struct dbm_directive *p, *match = NULL;

  mu_ltrim_class (val, MU_CTYPE_BLANK);
  mu_rtrim_class (val, MU_CTYPE_BLANK);
  len = strcspn (val, "=");
  for (p = dbm_directive_table; p->name; p++)
    {
      if (p->len == len && memcmp (p->name, val, len) == 0)
	{
	  int rc;
	  
	  match = p;
	  if (val[len] == '=')
	    {
	      if (!(p->flags & DF_VALUE))
		continue;
	    }
	  else if (p->flags & DF_VALUE)
	    continue;
		  
	  if (p->flags & DF_IGNORE)
	    return 0;
	  
	  if (p->flags & DF_SEEN)
	    {
	      mu_error (_("directive %s appeared twice"), val);
	      return 1;
	    }
	  rc = p->set ((p->flags & DF_VALUE) ? val + len + 1 : NULL);
	  if (p->flags & DF_HEADER)
	    p->flags |= DF_SEEN;
	  return rc;
	}
    }
  if (match)
    {
      if (match->flags & DF_VALUE)
	mu_error (_("directive requires argument: %s"), val);
      else
	mu_error (_("directive does not take argument: %s"), val);
    }
  else
    mu_error (_("unknown or unsupported directive %s"), val);
  return 1;
}

static void
print_header (const char *name, mu_stream_t str)
{
  char *delim;
  time_t t;

  time (&t);
  mu_stream_printf (str, "# Database dump file created by %s on %s",
		    PACKAGE_STRING,
		    ctime (&t));
  mu_stream_printf (str, "#:version=%s\n", format->name);
  if (!is_ignored_directive ("file"))
    mu_stream_printf (str, "#:file=%s\n", name);

  delim = "#:";
  if ((known_meta_data & META_UID) && !is_ignored_directive ("uid"))
    {
      mu_stream_printf (str, "%suid=%lu", delim, (unsigned long) owner_uid);
      delim = ",";
    }
  if ((known_meta_data & META_USER) && !is_ignored_directive ("user"))
    {
      mu_stream_printf (str, "%suser=%s", delim, owner_user);
      delim = ",";
    }
  
  if ((known_meta_data & META_GID) && !is_ignored_directive ("gid"))
    {
      mu_stream_printf (str, "%sgid=%lu", delim, (unsigned long) owner_gid);
      delim = ",";
    }

  if ((known_meta_data & META_GROUP) && !is_ignored_directive ("group"))
    {
      mu_stream_printf (str, "%sgroup=%s", delim, owner_group);
      delim = ",";
    }
  
  if ((known_meta_data & META_FILE_MODE) && !is_ignored_directive ("mode"))
    mu_stream_printf (str, "%smode=%03o", delim, file_mode);

  if (delim[0] == ',')
    mu_stream_printf (str, "\n");
}


static void
add_records (int mode, int replace)
{
  mu_dbm_file_t db;
  mu_stream_t instream, flt;
  const char *flt_argv[] = { "inline-comment", "#", "-S", "-i", "#", NULL };
  int rc;
  int save_log_mode = 0, log_mode;
  struct mu_locus_range save_locus = MU_LOCUS_RANGE_INITIALIZER,
                        locus = MU_LOCUS_RANGE_INITIALIZER;
  struct mu_dbm_datum key, contents;
  struct iobuf input;
  struct mu_wordsplit ws;
  int wsflags = MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | MU_WRDSF_DELIM;
  
  if (mode != MU_STREAM_CREAT)
    {
      ignore_flagged_directives (DF_META);
      known_meta_data = 0;
    }
  
  /* Prepare input data */
  if (input_file)
    {
      rc = mu_file_stream_create (&instream, input_file, MU_STREAM_READ);
      if (rc)
	{
	  mu_error (_("cannot open input file %s: %s"), input_file,
		    mu_strerror (rc));
	  exit (EX_NOINPUT);
	}
    }
  else
    {
      instream = mu_strin;
      mu_stream_ref (instream);
    }
  
  rc = mu_filter_create_args (&flt, instream, "inline-comment",
			      MU_ARRAY_SIZE (flt_argv) - 1, flt_argv,
			      MU_FILTER_DECODE, MU_STREAM_READ);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_filter_create_args", input_file, rc);
      exit (EX_UNAVAILABLE);
    }
  mu_stream_unref (instream);
  instream = flt;

  /* Configure error stream to output input file location before each error
     message */
  locus.beg.mu_file = input_file ? input_file : "<stdin>";
  locus.beg.mu_line = 0;
  locus.beg.mu_col = 0;
  memset (&locus.end, 0, sizeof locus.end);
  
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_GET_MODE,
		   &save_log_mode);
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
		   MU_IOCTL_LOGSTREAM_GET_LOCUS_RANGE,
		   &save_locus);
  log_mode = save_log_mode | MU_LOGMODE_LOCUS;
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE,
		   &log_mode);
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
		   MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE,
		   &locus);

  /* Initialize I/O data */
  memset (&key, 0, sizeof key);
  memset (&contents, 0, sizeof contents);

  /* Initialize input buffer */
  input.buffer = NULL;
  input.bufsize = 0;
  input.length = 0;
  input.flag = 0;
  input.stream = instream;

  /* Read directive header */
  ws.ws_delim = ",";
  while (!input_eof (&input)
	 && (rc = input_getline (&input)) == 0
	 && is_dbm_directive (&input)
	 && !is_len_directive (&input))
    {
      size_t i;

      if (mu_wordsplit (input.buffer + 2, &ws, wsflags))
	{
	  mu_error (_("cannot split input line: %s"),
		    mu_wordsplit_strerror (&ws));
	  exit (EX_SOFTWARE);
	}
      for (i = 0; i < ws.ws_wordc; i++)
	{
	  set_directive (ws.ws_wordv[i]);
	}
      wsflags |= MU_WRDSF_REUSE;
    }
  if (wsflags & MU_WRDSF_REUSE)
    mu_wordsplit_free (&ws);

  if (!format)
    format = DEFAULT_LOAD_FORMAT;
  init_format (1, &input);
  
  /* Open the database */
  db = open_db_file (mode);
  
  /* Read and store the actual data */
  if (rc == 0 && !input_eof (&input))
    {
      ignore_flagged_directives (DF_HEADER);
      input_ungetline (&input);

      memset (&key, 0, sizeof key);
      memset (&contents, 0, sizeof contents);
      
      while ((rc = read_data (&input, &key, &contents)) == 0)
	{
	  if (key.mu_dsize)
	    {
	      rc = mu_dbm_store (db, &key, &contents, replace);
	      if (rc)
		mu_error (_("cannot store datum: %s"),
			  rc == MU_ERR_FAILURE ?
			  mu_dbm_strerror (db) : mu_strerror (rc));
	    }
	}
    }
  
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM, MU_IOCTL_LOGSTREAM_SET_MODE,
		   &save_log_mode);
  mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
		   MU_IOCTL_LOGSTREAM_SET_LOCUS_RANGE,
		   &save_locus);
  
  if (known_meta_data)
    set_db_ownership (db);
  mu_dbm_destroy (&db);
  mu_stream_unref (instream);
}

static void
create_database ()
{
  if (getuid() != 0)
    ignore_flagged_directives (DF_META);
  
  if (copy_permissions)
    {
      struct stat st;
      
      if (!input_file)
	{
	  mu_error (_("--copy-permissions used without --file"));
	  exit (EX_USAGE);
	}
      if (stat (input_file, &st))
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "stat", input_file, errno);
	  exit (EX_UNAVAILABLE);
	}
      owner_uid = st.st_uid;
      owner_gid = st.st_gid;
      file_mode = st.st_mode & 0777;
      known_meta_data |= META_UID | META_GID | META_FILE_MODE;
    }
  else if (auth)
    {
      if (!(known_meta_data & META_UID))
	{
	  owner_uid = auth->uid;
	  known_meta_data |= META_UID;
	}
      if (!(known_meta_data & META_GID))
	{
	  owner_gid = auth->gid;
	  known_meta_data |= META_GID;
	}
    }
  add_records (MU_STREAM_CREAT, 0);
}

static int
store_to_list (struct mu_dbm_datum const *key, void *data)
{
  int rc;
  mu_list_t list = data;
  char *p = mu_alloc (key->mu_dsize + 1);
  memcpy (p, key->mu_dptr, key->mu_dsize);
  p[key->mu_dsize] = 0;
  rc = mu_list_append (list, p);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", p, rc);
      exit (EX_SOFTWARE);
    }
  return 0;
}

static int
do_delete (void *item, void *data)
{
  char *str = item;
  mu_dbm_file_t db = data;
  struct mu_dbm_datum key;
  int rc;

  init_datum (&key, str);
  rc = mu_dbm_delete (db, &key);
  if (rc == MU_ERR_NOENT)
    {
      mu_error (_("cannot remove record for %s: %s"),
		str, mu_strerror (rc));
    }
  else if (rc)
    {
      mu_error (_("cannot remove record for %s: %s"),
		str, mu_dbm_strerror (db));
      if (rc != MU_ERR_NOENT)
	exit (EX_UNAVAILABLE);
    }
  return 0;
}

static void
delete_database (int argc, char **argv)
{
  mu_dbm_file_t db = open_db_file (MU_STREAM_RDWR);
  mu_list_t templist = NULL;
  int rc, i;
  
  if (argc == 0)
    {
      mu_error (_("not enough arguments for delete"));
      exit (EX_USAGE);
    }

  /* Collect matching keys */
  rc = mu_list_create (&templist);
  if (rc)
    {
      mu_diag_funcall (MU_DIAG_ERROR, "mu_list_create", NULL, rc);
      exit (EX_SOFTWARE);
    }
  mu_list_set_destroy_item (templist, mu_list_free_item);
  switch (key_type)
    {
    case key_literal:
      for (i = 0; i < argc; i++)
	{
	  char *p = mu_strdup (argv[i]);
	  rc = mu_list_append (templist, p);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERROR, "mu_list_append", p, rc);
	      exit (EX_SOFTWARE);
	    }
	}
      break;

    case key_glob:
      iterate_database (db, match_glob, argv,
			store_to_list, templist);
      break;

    case key_regex:
      {
	struct regmatch m;
	
	compile_regexes (argc, argv, &m);
	iterate_database (db, match_regex, &m, store_to_list, templist);
	free_regexes (&m);
      }
    }
  mu_list_foreach (templist, do_delete, db);
  mu_list_destroy (&templist);
  mu_dbm_destroy (&db);
}

/*
  mu dbm --create a.db < input
  mu dbm --list a.db
  mu dbm --delete a.db key [key...]
  mu dbm --add a.db < input
  mu dbm --replace a.db < input
 */

static void
set_permissions (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  char *p;
  unsigned long d = strtoul (arg, &p, 8);
  if (*p || (d & ~0777))
    {
      mu_parseopt_error (po, _("invalid file mode: %s"), arg);
      exit (po->po_exit_error);
    }
  file_mode = d;
  known_meta_data |= META_FILE_MODE;
}

static void
set_user (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  auth = mu_get_auth_by_name (arg);
  if (auth)
    known_meta_data |= META_AUTH;
  else
    {
      char *p;
      unsigned long n = strtoul (arg, &p, 0);
      if (*p == 0)
	{
	  owner_uid = n;
	  known_meta_data |= META_UID;
	}
      else
	{
	  mu_parseopt_error (po, _("no such user: %s"), arg);
	  exit (po->po_exit_error);
	}
    }
  ignore_directives ("user,uid");
}

static void
set_group (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  struct group *gr = getgrnam (arg);
  if (!gr)
    {
      char *p;
      unsigned long n = strtoul (arg, &p, 0);
      if (*p == 0)
	owner_gid = n;
      else
	{
	  mu_parseopt_error (po, _("no such group: %s"), arg);
	  exit (po->po_exit_error);
	}
    }
  else
    owner_gid = gr->gr_gid;
  known_meta_data |= META_GID;
  ignore_directives ("group,gid");
}

static void
set_ignore_meta (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  ignore_flagged_directives (DF_META);
}

static void
set_ignore_directives (struct mu_parseopt *po, struct mu_option *opt,
		       char const *arg)
{
  ignore_directives (arg);
}

static void
set_format (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  select_format (arg);
}

static void
set_glob (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  key_type = key_glob;
}

static void
set_regex (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  key_type = key_regex;
}

static void
set_ignore_case (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
  case_sensitive = 0;
}

static void
clear_include_zero (struct mu_parseopt *po, struct mu_option *opt,
		    char const *arg)
{
  include_zero = 0;
}

static struct mu_option dbm_options[] = {
  MU_OPTION_GROUP (N_("Create Options")),
  { "file",   'f', N_("FILE"), MU_OPTION_DEFAULT,
    /* TRANSLATORS: names in parentheses are dbm commands: don't translate
       them. */
    N_("read input from FILE (with create, delete, add and replace)"),
    mu_c_string, &input_file },
  { "permissions", 'p', N_("NUM"), MU_OPTION_DEFAULT,
    N_("set permissions on the created database"),
    mu_c_string, NULL, set_permissions },
  { "user",        'u', N_("USER"), MU_OPTION_DEFAULT,
    N_("set database owner name"),
    mu_c_string, NULL, set_user },
  { "group",       'g', N_("GROUP"), MU_OPTION_DEFAULT,
    N_("set database owner group"),
    mu_c_string, NULL, set_group },
  { "copy-permissions", 'P', NULL, MU_OPTION_DEFAULT,
    N_("copy database permissions and ownership from the input file"),
    mu_c_bool, &copy_permissions },
  { "ignore-meta",      'm', NULL, MU_OPTION_DEFAULT,
    N_("ignore meta-information from input file headers"),
    mu_c_string, NULL, set_ignore_meta },
  { "ignore-directives", 'I', N_("NAMES"), MU_OPTION_DEFAULT,
    N_("ignore the listed directives"),
    mu_c_string, NULL, set_ignore_directives },

  /* TRANSLATORS: list and dump are dbm command names. */
  MU_OPTION_GROUP (N_("List and Dump Options")),
  { "format",           'H', N_("TYPE"), MU_OPTION_DEFAULT,
    N_("select output format"),
    mu_c_string, NULL, set_format },
  { "no-header",        'q', NULL, MU_OPTION_DEFAULT,
    N_("suppress format header"),
    mu_c_bool, &suppress_header },
  
  /* TRANSLATORS: list, dump, and delete are dbm command names. */
  MU_OPTION_GROUP (N_("List, Dump and Delete Options")),
  { "glob",        'G', NULL, MU_OPTION_DEFAULT,
    N_("treat keys as globbing patterns"),
    mu_c_string, NULL, set_glob },
  { "regex",       'R', NULL, MU_OPTION_DEFAULT,
    N_("treat keys as regular expressions"),
    mu_c_string, NULL, set_regex },
  { "ignore-case", 'i', NULL, MU_OPTION_DEFAULT,
    N_("case-insensitive matches"),
    mu_c_string, NULL, set_ignore_case },
  
  MU_OPTION_GROUP (N_("Options for Use with Format 0.0")),
  { "count-null",  'N', NULL, MU_OPTION_DEFAULT,
    N_("data length accounts for terminating zero"),
    mu_c_bool, &include_zero },
  { "no-count-null", 'n', NULL, MU_OPTION_DEFAULT,
    N_("data length does not account for terminating zero"),
    mu_c_string, NULL, clear_include_zero },
  MU_OPTION_END
};

struct mu_kwd mode_tab[] =
{
  { "add",     mode_add },
  { "create",  mode_create },
  { "load",    mode_create },
  { "list",    mode_list },
  { "replace", mode_replace },
  { "delete",  mode_delete },
  { "dump",    mode_dump },
  { NULL }
};

int
main (int argc, char **argv)
{
  int index;
    
  mu_action_getopt (&argc, &argv, dbm_options, dbm_doc, dbm_args_doc);

  if (argc == 0)
    {
      mu_error (_("subcommand not given"));
      exit (EX_USAGE);
    }
  
  if (mu_kwd_xlat_name (mode_tab, argv[0], &index))
    {
      mu_error (_("unknown subcommand: %s"), argv[0]);
      exit (EX_USAGE);
    }
  mode = index;
  argc--;
  argv++;
  
  if (argc == 0)
    {
      if (mode != mode_create)
	{
	  mu_error (_("database name not given"));
	  exit (EX_USAGE);
	}
    }
  else
    {
      db_name = *argv++;
      --argc;
    }
  
  switch (mode)
    {
    case mode_list:
      if (!format)
	format = DEFAULT_LIST_FORMAT;
      if (suppress_header == -1)
	suppress_header = 1;
      list_database (argc, argv);
      break;

    case mode_dump:
      if (!format)
	format = DEFAULT_DUMP_FORMAT;
      if (suppress_header == -1)
	suppress_header = 0;
      list_database (argc, argv);
      break;
      
    case mode_create:
      if (argc)
	{
	  mu_error (_("too many arguments for create"));
	  exit (EX_USAGE);
	}
      create_database ();
      break;
      
    case mode_delete:
      delete_database (argc, argv);
      break;
      
    case mode_add:
      if (argc)
	{
	  mu_error (_("too many arguments for add"));
	  exit (EX_USAGE);
	}
      add_records (MU_STREAM_RDWR, 0);
      break;
      
    case mode_replace:
      if (argc)
	{
	  mu_error (_("too many arguments for replace"));
	  exit (EX_USAGE);
	}
      add_records (MU_STREAM_RDWR, 1);
      break;
    }
  return 0;
}

