/* uuconv.c
   Convert one type of UUCP configuration file to another.

   Copyright (C) 1991, 1992, 1993, 1994, 1995, 2002 Ian Lance Taylor

   This file is part of the Taylor UUCP package.

   This program 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 2 of the
   License, or (at your option) any later version.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.

   The author of the program may be contacted at ian@airs.com.
   */

#include "uucnfi.h"

#if USE_RCS_ID
const char uuconv_rcsid[] = "$Id: uuconv.c,v 1.30 2002/03/05 19:10:42 ian Rel $";
#endif

#include "getopt.h"

/* Local functions.  */

static void uvusage P((void));
static void uvhelp P((void));
static void uvwrite_time P((FILE *e, struct uuconf_timespan *qtime));
static void uvwrite_string P((FILE *e, const char *zarg, const char *zcmd));
static void uvwrite_size P((FILE *e, struct uuconf_timespan *qsize,
			    const char *zcmd));
static void uvwrite_boolean P((FILE *e, int iarg, const char *zcmd));
static void uvwrite_string_array P((FILE *e, char **pz, const char *zcmd));
static void uvwrite_chat_script P((FILE *e, char **pz));
static void uvwrite_chat P((FILE *e, const struct uuconf_chat *qchat,
			    const struct uuconf_chat *qlast,
			    const char *zprefix, boolean fforce));
static void uvwrite_proto_params P((FILE *e,
				    const struct uuconf_proto_param *qparam,
				    const char *zprefix));
static void uvwrite_taylor_system P((FILE *e,
				     const struct uuconf_system *qsys));
static void uvwrite_v2_system P((FILE *e,
				 const struct uuconf_system *qsys));
static void uvwrite_hdb_system P((FILE *e,
				  const struct uuconf_system *qsys));
static boolean fvperm_string_cmp P((const char *z1, const char *z2));
static boolean fvperm_array_cmp P((const char **pz1, const char **pz2));
static void uvadd_perm P((struct shpermissions *qadd));
static void uvwrite_perms P((void));
static void uvwrite_perm_array P((FILE *e, const char **pz,
				  const char *zcmd, size_t *pccol));
static void uvwrite_perm_boolean P((FILE *e, int f, const char *zcmd,
				    size_t *pccol, boolean fsendfiles));
static void uvwrite_perm_rw_array P((FILE *e, const char **pz,
				     const char *zcmd, size_t *pccol));
static void uvwrite_perm_string P((FILE *e, const char *z, const char *zcmd,
				   size_t *pccol));
static int ivwrite_taylor_port P((struct uuconf_port *qport,
				  pointer pinfo));
static int ivwrite_v2_port P((struct uuconf_port *qport, pointer pinfo));
static int ivwrite_hdb_port P((struct uuconf_port *qport, pointer pinfo));
static void uvwrite_taylor_port P((FILE *e, struct uuconf_port *qport,
				   const char *zprefix));
static void uvwrite_taylor_dialer P((FILE *e, struct uuconf_dialer *qdialer,
				     const char *zprefix));
static void uvwrite_hdb_dialer P((FILE *e, struct uuconf_dialer *qdialer));
static void uvuuconf_error P((pointer puuconf, int iret));

/* The program name.  */
const char *zProgram;

/* A list of Permissions entries built when writing out HDB system
   information.  */
static struct shpermissions *qVperms;

/* Type of configuration file.  */
enum tconfig
{
  CONFIG_TAYLOR,
  CONFIG_V2,
  CONFIG_HDB
};

/* Long getopt options.  */
static const struct option asVlongopts[] =
{
  { "input", required_argument, NULL, 'i' },
  { "output", required_argument, NULL, 'o' },
  { "program", required_argument, NULL, 'p' },
  { "config", required_argument, NULL, 'I' },
  { "debug", required_argument, NULL, 'x' },
  { "version", no_argument, NULL, 'v' },
  { "help", no_argument, NULL, 1 },
  { NULL, 0, NULL, 0 }
};

int
main (argc, argv)
     int argc;
     char **argv;
{
  /* -I: The configuration file name.  */
  const char *zconfig = NULL;
  /* -i: Input type.  */
  const char *zinput = NULL;
  /* -o: Output type.  */
  const char *zoutput = NULL;
  /* -p: Program name.  */
  const char *zprogram = NULL;
  int iopt;
  enum tconfig tinput, toutput;
  int iret;
  pointer pinput;

  zProgram = argv[0];

  while ((iopt = getopt_long (argc, argv, "i:I:o:p:vx:", asVlongopts,
			      (int *) NULL)) != EOF)
    {
      switch (iopt)
	{
	case 'i':
	  /* Input type.  */
	  zinput = optarg;
	  break;

	case 'o':
	  /* Output type.  */
	  zoutput = optarg;
	  break;

	case 'p':
	  /* Program name.  */
	  zprogram = optarg;
	  break;

	case 'I':
	  /* Set the configuration file name.  */
	  zconfig = optarg;
	  break;

	case 'x':
	  /* Set the debugging level.  */
	  break;

	case 'v':
	  /* Print version and exit.  */
	  printf ("uuconv (Taylor UUCP) %s\n", VERSION);
	  printf ("Copyright (C) 1991, 92, 93, 94, 1995, 2002 Ian Lance Taylor\n");
	  printf ("This program is free software; you may redistribute it under the terms of\n");
	  printf ("the GNU General Public LIcense.  This program has ABSOLUTELY NO WARRANTY.\n");
	  exit (EXIT_SUCCESS);
	  /*NOTREACHED*/

	case 1:
	  /* --help.  */
	  uvhelp ();
	  exit (EXIT_SUCCESS);
	  /*NOTREACHED*/

	case 0:
	  /* Long option found, and flag value set.  */
	  break;

	default:
	  uvusage ();
	  break;
	}
    }

  if (optind != argc
      || zinput == NULL
      || zoutput == NULL)
    uvusage ();

  if (strcasecmp (zinput, "taylor") == 0)
    tinput = CONFIG_TAYLOR;
  else if (strcasecmp (zinput, "v2") == 0)
    tinput = CONFIG_V2;
  else if (strcasecmp (zinput, "hdb") == 0)
    tinput = CONFIG_HDB;
  else
    {
      uvusage ();
      tinput = CONFIG_TAYLOR;
    }

  if (strcasecmp (zoutput, "taylor") == 0)
    toutput = CONFIG_TAYLOR;
  else if (strcasecmp (zoutput, "v2") == 0)
    toutput = CONFIG_V2;
  else if (strcasecmp (zoutput, "hdb") == 0)
    toutput = CONFIG_HDB;
  else
    {
      uvusage ();
      toutput = CONFIG_TAYLOR;
    }

  if (tinput == toutput)
    uvusage ();

  iret = UUCONF_SUCCESS;

  /* Initialize the input.  */
  pinput = NULL;
  switch (tinput)
    {
    case CONFIG_TAYLOR:
      iret = uuconf_taylor_init (&pinput, zprogram, zconfig);
      break;
    case CONFIG_V2:
      iret = uuconf_v2_init (&pinput);
      break;
    case CONFIG_HDB:
      iret = uuconf_hdb_init (&pinput, zprogram);
      break;
    }
  if (iret != UUCONF_SUCCESS)
    {
      uvuuconf_error (pinput, iret);
      exit (EXIT_FAILURE);
    }

  {
    char **pzsystems;
    char *zsys;
    char abtaylor[sizeof ZCURDIR + sizeof SYSFILE - 1];
    char abv2[sizeof ZCURDIR + sizeof V2_SYSTEMS - 1];
    char abhdb[sizeof ZCURDIR + sizeof HDB_SYSTEMS - 1];
    FILE *esys;
    char **pz;

    /* Get the list of systems.  */
    switch (tinput)
      {
      case CONFIG_TAYLOR:
	iret = uuconf_taylor_system_names (pinput, &pzsystems, FALSE);
	break;
      case CONFIG_V2:
	iret = uuconf_v2_system_names (pinput, &pzsystems, FALSE);
	break;
      case CONFIG_HDB:
	iret = uuconf_hdb_system_names (pinput, &pzsystems, FALSE);
	break;
      }
    if (iret != UUCONF_SUCCESS)
      uvuuconf_error (pinput, iret);
    else
      {
	/* Open the sys file for the output type.  */
	switch (toutput)
	  {
	  default:
	  case CONFIG_TAYLOR:
	    sprintf (abtaylor, "%s%s", ZCURDIR, SYSFILE);
	    zsys = abtaylor;
	    break;
	  case CONFIG_V2:
	    sprintf (abv2, "%s%s", ZCURDIR, V2_SYSTEMS);
	    zsys = abv2;
	    break;
	  case CONFIG_HDB:
	    sprintf (abhdb, "%s%s", ZCURDIR, HDB_SYSTEMS);
	    zsys = abhdb;
	    break;
	  }
	esys = fopen (zsys, "w");
	if (esys == NULL)
	  {
	    fprintf (stderr, "uuchk:%s: ", zsys);
	    perror ("fopen");
	    exit (EXIT_FAILURE);
	  }

	fprintf (esys, "# %s file automatically generated by uuconv.\n",
		 zsys);

	/* Read and write each system.  We cheat and call the internal
	   routines, so that we can easily detect default information and
	   not write it out.  This isn't necessary, but it makes the output
	   smaller and simpler.  */
	for (pz = pzsystems; *pz != NULL; pz++)
	  {
	    struct uuconf_system ssys;

	    switch (tinput)
	      {
	      case CONFIG_TAYLOR:
		iret = _uuconf_itaylor_system_internal (pinput, *pz, &ssys);
		break;
	      case CONFIG_V2:
		iret = _uuconf_iv2_system_internal (pinput, *pz, &ssys);
		break;
	      case CONFIG_HDB:
		iret = _uuconf_ihdb_system_internal (pinput, *pz, &ssys);
		break;
	      }
	    if (iret != UUCONF_SUCCESS)
	      uvuuconf_error (pinput, iret);
	    else
	      {
		switch (toutput)
		  {
		  case CONFIG_TAYLOR:
		    uvwrite_taylor_system (esys, &ssys);
		    break;
		  case CONFIG_V2:
		    uvwrite_v2_system (esys, &ssys);
		    break;
		  case CONFIG_HDB:
		    uvwrite_hdb_system (esys, &ssys);
		    break;
		  }
		if (toutput != CONFIG_HDB)
		  (void) uuconf_system_free (pinput, &ssys);
	      }
	  }

	if (toutput == CONFIG_HDB)
	  uvwrite_perms ();

	if (ferror (esys)
	    || fclose (esys) == EOF)
	  {
	    fprintf (stderr, "uuchk:%s: error during output\n", zsys);
	    exit (EXIT_FAILURE);
	  }
      }
  }

  {
    /* Open the port file for the output type.  */
    char *zport;
    char abtaylor[sizeof ZCURDIR + sizeof PORTFILE - 1];
    char abv2[sizeof ZCURDIR + sizeof V2_DEVICES - 1];
    char abhdb[sizeof ZCURDIR + sizeof HDB_DEVICES - 1];
    FILE *eport;
    int (*piportfn) P((struct uuconf_port *, pointer));
    struct uuconf_port sport;

    switch (toutput)
      {
      default:
      case CONFIG_TAYLOR:
	sprintf (abtaylor, "%s%s", ZCURDIR, PORTFILE);
	zport = abtaylor;
	piportfn = ivwrite_taylor_port;
	break;
      case CONFIG_V2:
	sprintf (abv2, "%s%s", ZCURDIR, V2_DEVICES);
	zport = abv2;
	piportfn = ivwrite_v2_port;
	break;
      case CONFIG_HDB:
	sprintf (abhdb, "%s%s", ZCURDIR, HDB_DEVICES);
	zport = abhdb;
	piportfn = ivwrite_hdb_port;
	break;
      }
    eport = fopen (zport, "w");
    if (eport == NULL)
      {
	fprintf (stderr, "uuchk:%s: ", zport);
	perror ("fopen");
	exit (EXIT_FAILURE);
      }

    fprintf (eport, "# %s file automatically generated by uuconv.\n", zport);

    switch (tinput)
      {
      case CONFIG_TAYLOR:
	iret = uuconf_taylor_find_port (pinput, (const char *) NULL, 0L,
					0L, piportfn, (pointer) eport,
					&sport);
	break;
      case CONFIG_V2:
	iret = uuconf_v2_find_port (pinput, (const char *) NULL, 0L, 0L,
				    piportfn, (pointer) eport, &sport);
	break;
      case CONFIG_HDB:
	iret = uuconf_hdb_find_port (pinput, (const char *) NULL, 0L, 0L,
				     piportfn, (pointer) eport, &sport);
	break;
      }
	
    if (iret != UUCONF_NOT_FOUND)
      uvuuconf_error (pinput, iret);

    if (ferror (eport)
	|| fclose (eport) == EOF)
      {
	fprintf (stderr, "uuchk:%s: error during output\n", zport);
	exit (EXIT_FAILURE);
      }
  }

  /* V2 configuration files don't support dialers.  */
  if (tinput != CONFIG_V2 && toutput != CONFIG_V2)
    {
      char **pzdialers;
      char *zdialer;
      char abtaylor[sizeof ZCURDIR + sizeof DIALFILE - 1];
      char abhdb[sizeof ZCURDIR + sizeof HDB_DIALERS - 1];
      FILE *edialer;
      char **pz;

      /* Get the list of dialers.  */
      switch (tinput)
	{
	default:
	case CONFIG_TAYLOR:
	  iret = uuconf_taylor_dialer_names (pinput, &pzdialers);
	  break;
	case CONFIG_HDB:
	  iret = uuconf_hdb_dialer_names (pinput, &pzdialers);
	  break;
	}
      if (iret != UUCONF_SUCCESS)
	uvuuconf_error (pinput, iret);
      else
	{
	  /* Open the sys file for the output type.  */
	  switch (toutput)
	    {
	    default:
	    case CONFIG_TAYLOR:
	      sprintf (abtaylor, "%s%s", ZCURDIR, DIALFILE);
	      zdialer = abtaylor;
	      break;
	    case CONFIG_HDB:
	      sprintf (abhdb, "%s%s", ZCURDIR, HDB_DIALERS);
	      zdialer = abhdb;
	      break;
	    }
	  edialer = fopen (zdialer, "w");
	  if (edialer == NULL)
	    {
	      fprintf (stderr, "uuchk:%s: ", zdialer);
	      perror ("fopen");
	      exit (EXIT_FAILURE);
	    }

	  fprintf (edialer, "# %s file automatically generated by uuconv.\n",
		   zdialer);

	  /* Read and write each dialer.  */
	  for (pz = pzdialers; *pz != NULL; pz++)
	    {
	      struct uuconf_dialer sdialer;

	      switch (tinput)
		{
		default:
		case CONFIG_TAYLOR:
		  iret = uuconf_taylor_dialer_info (pinput, *pz, &sdialer);
		  break;
		case CONFIG_HDB:
		  iret = uuconf_hdb_dialer_info (pinput, *pz, &sdialer);
		  break;
		}
	      if (iret != UUCONF_SUCCESS)
		uvuuconf_error (pinput, iret);
	      else
		{
		  switch (toutput)
		    {
		    default:
		    case CONFIG_TAYLOR:
		      fprintf (edialer, "# Start of dialer %s\n",
			       sdialer.uuconf_zname);
		      fprintf (edialer, "dialer %s\n", sdialer.uuconf_zname);
		      uvwrite_taylor_dialer (edialer, &sdialer, "");
		      break;
		    case CONFIG_HDB:
		      uvwrite_hdb_dialer (edialer, &sdialer);
		      break;
		    }
		  (void) uuconf_dialer_free (pinput, &sdialer);
		}
	    }

	  if (ferror (edialer)
	      || fclose (edialer) == EOF)
	    {
	      fprintf (stderr, "uuchk:%s: error during output\n", zdialer);
	      exit (EXIT_FAILURE);
	    }
	}
    }

  exit (EXIT_SUCCESS);

  /* Avoid complaints about not returning.  */
  return 0;
}

/* Print out a usage message and die.  */

static void
uvusage ()
{
  fprintf (stderr, "Usage: %s -i input-type -o output-type [-p program]\n",
	   zProgram);
  fprintf (stderr, "Use %s --help for help\n", zProgram);
  exit (EXIT_FAILURE);
}

/* Print a help message.  */

static void
uvhelp ()
{
  printf ("Taylor UUCP %s, copyright (C) 1991, 92, 93, 94, 1995, 2002 Ian Lance Taylor\n",
	  VERSION);
  printf ("Converts UUCP configuration files from one format to another.\n");
  printf ("Usage: %s -i input -o output [-p program] [-I file]\n", zProgram);
  printf (" -i,--input input: Set input type (one of taylor, v2, hdb)\n");
  printf (" -o,--output output: Set output type (one of taylor, v2, hdb)\n");
  printf (" -p,--program program: Program to convert (e.g., uucp or cu)\n");
  printf (" -I,--config file: Set Taylor UUCP configuration file to use\n");
  printf (" -v,--version: Print version and exit\n");
  printf (" --help: Print help and exit\n");
  printf ("Report bugs to taylor-uucp@gnu.org\n");
}

/* Write out a timespan.  */

static void
uvwrite_time (e, qtime)
     FILE *e;
     struct uuconf_timespan *qtime;
{
  if (qtime == NULL)
    {
      fprintf (e, "Never");
      return;
    }

  if (qtime->uuconf_istart == 0 && qtime->uuconf_iend == 7 * 24 * 60)
    {
      fprintf (e, "Any");
      return;
    }

  for (; qtime != NULL; qtime = qtime->uuconf_qnext)
    {
      int idaystart, idayend;
      int ihourstart, ihourend;
      int iminutestart, iminuteend;
      const char * const zdays = "Su\0Mo\0Tu\0We\0Th\0Fr\0Sa";

      idaystart = qtime->uuconf_istart / (24 * 60);
      ihourstart = (qtime->uuconf_istart % (24 * 60)) / 60;
      iminutestart = qtime->uuconf_istart % 60;
      if (qtime->uuconf_iend >= 7 * 24 * 60)
	qtime->uuconf_iend = 7 * 24 * 60 - 1;
      idayend = qtime->uuconf_iend / (24 * 60);
      ihourend = (qtime->uuconf_iend % (24 * 60)) / 60;
      iminuteend = qtime->uuconf_iend % 60;
      if (ihourend == 0 && iminuteend == 0)
	--idayend;

      if (idaystart == idayend)
	fprintf (e, "%s%02d%02d-%02d%02d", zdays + idaystart * 3,
		 ihourstart, iminutestart, ihourend, iminuteend);
      else
	{
	  int i;

	  fprintf (e, "%s%02d%02d-0000", zdays + idaystart * 3,
		   ihourstart, iminutestart);
	  for (i = idaystart + 1; i < idayend; i++)
	    fprintf (e, ",%s", zdays + i * 3);
	  if (ihourend != 0 || iminuteend != 0)
	    fprintf (e, ",%s0000-%02d%02d", zdays + idayend * 3, ihourend,
		     iminuteend);
	}

      if (qtime->uuconf_qnext != NULL)
	fprintf (e, ",");
    }
}

/* Some subroutines used when writing out Taylor UUCP configuration
   files.  */

/* Write a command with a string argument.  */

static void
uvwrite_string (e, zarg, zcmd)
     FILE *e;
     const char *zarg;
     const char *zcmd;
{
  if (zarg != (const char *) &_uuconf_unset)
    fprintf (e, "%s %s\n", zcmd, zarg == NULL ? (const char *) "" : zarg);
}

/* Write out a size restriction command.  */

static void
uvwrite_size (e, qtime, zcmd)
     FILE *e;
     struct uuconf_timespan *qtime;
     const char *zcmd;
{
  if (qtime != (struct uuconf_timespan *) &_uuconf_unset)
    {
      for (; qtime != NULL; qtime = qtime->uuconf_qnext)
	{
	  fprintf (e, "%s %ld", zcmd, qtime->uuconf_ival);
	  uvwrite_time (e, qtime);
	  fprintf (e, "\n");
	}
    }
}

/* Write out a boolean argument with a string command.  If the value
   is less than zero, than it was uninitialized and we don't write
   anything.  */

static void
uvwrite_boolean (e, fval, zcmd)
     FILE *e;
     int fval;
     const char *zcmd;
{
  if (fval >= 0)
    fprintf (e, "%s %s\n", zcmd, fval > 0 ? "true" : "false");
}

/* Write out a string array as a single command.  */

static void
uvwrite_string_array (e, pz, zcmd)
     FILE *e;
     char **pz;
     const char *zcmd;
{
  if (pz != (char **) &_uuconf_unset)
    {
      fprintf (e, "%s", zcmd);
      if (pz != NULL)
	for (; *pz != NULL; pz++)
	  fprintf (e, " %s", *pz);
      fprintf (e, "\n");
    }
}

/* Write out a chat script.  Don't separate subsend/subexpect strings
   by spaces.  */

static void
uvwrite_chat_script (e, pzarg)
     FILE *e;
     char **pzarg;
{
  char **pz;

  if (pzarg == NULL || pzarg == (char **) &_uuconf_unset)
    return;

  for (pz = pzarg; *pz != NULL; pz++)
    {
      if ((*pz)[0] != '-' && pz != pzarg)
	fprintf (e, " ");
      fprintf (e, *pz);
    }
}

/* Write out chat information.  If the qlast argument is not NULL,
   then only values that are different from qlast should be written.
   The fforce argument is used to get around a peculiar problem: if
   the ``chat'' command is used with no arguments for a system, then
   uuconf_pzchat will be NULL (not &_uuconf_unset) and the default
   chat script will not be used.  We must distinguish this case from
   the ``chat'' command not appearing at all for a port or dialer, in
   which case the value will again be NULL.  In the former case we
   must output a ``chat'' command, in the latter case we would prefer
   not to.  */

static void
uvwrite_chat (e, q, qlast, zprefix, fforce)
     FILE *e;
     const struct uuconf_chat *q;
     const struct uuconf_chat *qlast;
     const char *zprefix;
     boolean fforce;
{
  char **pz;
  char ab[100];

  if (q->uuconf_pzchat != (char **) &_uuconf_unset
      && (qlast == NULL
	  ? (fforce || q->uuconf_pzchat != NULL)
	  : qlast->uuconf_pzchat != q->uuconf_pzchat))
    {
      fprintf (e, "%schat ", zprefix);
      uvwrite_chat_script (e, q->uuconf_pzchat);
      fprintf (e, "\n");
    }

  if (q->uuconf_pzprogram != (char **) &_uuconf_unset
      && (qlast == NULL
	  ? q->uuconf_pzprogram != NULL
	  : qlast->uuconf_pzprogram != q->uuconf_pzprogram))
    {
      sprintf (ab, "%schat-program", zprefix);
      uvwrite_string_array (e, q->uuconf_pzprogram, ab);
    }

  if (q->uuconf_ctimeout >= 0
      && (qlast == NULL
	  || qlast->uuconf_ctimeout != q->uuconf_ctimeout))
    fprintf (e, "%schat-timeout %d\n", zprefix, q->uuconf_ctimeout);

  if (q->uuconf_pzfail != NULL
      && q->uuconf_pzfail != (char **) &_uuconf_unset
      && (qlast == NULL
	  || qlast->uuconf_pzfail != q->uuconf_pzfail))
    for (pz = q->uuconf_pzfail; *pz != NULL; pz++)
      fprintf (e, "%schat-fail %s\n", zprefix, *pz);
      
  if (qlast == NULL || qlast->uuconf_fstrip != q->uuconf_fstrip)
    {
      sprintf (ab, "%schat-strip", zprefix);
      uvwrite_boolean (e, q->uuconf_fstrip, ab);
    }
}

/* Write out protocol parameters to a Taylor UUCP file.  */

static void
uvwrite_proto_params (e, qparams, zprefix)
     FILE *e;
     const struct uuconf_proto_param *qparams;
     const char *zprefix;
{
  const struct uuconf_proto_param *qp;

  if (qparams == NULL
      || qparams == (struct uuconf_proto_param *) &_uuconf_unset)
    return;

  for (qp = qparams; qp->uuconf_bproto != '\0'; qp++)
    {
      const struct uuconf_proto_param_entry *qe;

      for (qe = qp->uuconf_qentries; qe->uuconf_cargs > 0; qe++)
	{
	  int i;

	  fprintf (e, "%sprotocol-parameter %c", zprefix, qp->uuconf_bproto);
	  for (i = 0; i < qe->uuconf_cargs; i++)
	    fprintf (e, " %s", qe->uuconf_pzargs[i]);
	  fprintf (e, "\n");
	}
    }
}

/* Write out Taylor UUCP system information.  */

static void
uvwrite_taylor_system (e, q)
     FILE *e;
     const struct uuconf_system *q;
{
  char **pz;
  const struct uuconf_system *qlast;

  fprintf (e, "# Start of system %s\n", q->uuconf_zname);

  fprintf (e, "system %s\n", q->uuconf_zname);
  if (q->uuconf_pzalias != NULL
      && q->uuconf_pzalias != (char **) &_uuconf_unset)
    for (pz = q->uuconf_pzalias; *pz != NULL; pz++)
      uvwrite_string (e, *pz, "alias");

  for (qlast = NULL; q != NULL; qlast = q, q = q->uuconf_qalternate)
    {
      struct uuconf_timespan *qtime;

      if (qlast != NULL)
	{
	  fprintf (e, "alternate");
	  if (q->uuconf_zalternate != (char *) &_uuconf_unset
	      && q->uuconf_zalternate != NULL)
	    fprintf (e, " %s", q->uuconf_zalternate);
	  fprintf (e, "\n");
	}

#define CHANGED(x) (qlast == NULL || qlast->x != q->x)

      if (CHANGED (uuconf_qtimegrade)
	  && (q->uuconf_qtimegrade
	      != (struct uuconf_timespan *) &_uuconf_unset))
	{
	  if (q->uuconf_qtimegrade == NULL)
	    fprintf (e, "time never\n");
	  else
	    {
	      for (qtime = q->uuconf_qtimegrade;
		   qtime != NULL;
		   qtime = qtime->uuconf_qnext)
		{
		  if ((char) qtime->uuconf_ival == UUCONF_GRADE_LOW)
		    fprintf (e, "time ");
		  else
		    fprintf (e, "timegrade %c ", (char) qtime->uuconf_ival);
		  uvwrite_time (e, qtime);
		  if (qtime->uuconf_cretry != 0)
		    fprintf (e, " %d", qtime->uuconf_cretry);
		  fprintf (e, "\n");
		}
	    }
	}

      if (CHANGED (uuconf_qcalltimegrade)
	  && (q->uuconf_qcalltimegrade
	      != (struct uuconf_timespan *) &_uuconf_unset))
	{
	  for (qtime = q->uuconf_qcalltimegrade;
	       qtime != NULL;
	       qtime = qtime->uuconf_qnext)
	    {
	      fprintf (e, "call-timegrade %c ", (char) qtime->uuconf_ival);
	      uvwrite_time (e, qtime);
	      fprintf (e, "\n");
	    }
	}

      if (CHANGED (uuconf_qcalledtimegrade)
	  && (q->uuconf_qcalledtimegrade
	      != (struct uuconf_timespan *) &_uuconf_unset))
	{
	  for (qtime = q->uuconf_qcalledtimegrade;
	       qtime != NULL;
	       qtime = qtime->uuconf_qnext)
	    {
	      fprintf (e, "called-timegrade %c ", (char) qtime->uuconf_ival);
	      uvwrite_time (e, qtime);
	      fprintf (e, "\n");
	    }
	}

      if (CHANGED (uuconf_qcall_local_size))
	uvwrite_size (e, q->uuconf_qcall_local_size, "call-local-size");

      if (CHANGED (uuconf_qcall_remote_size))
	uvwrite_size (e, q->uuconf_qcall_remote_size, "call-remote-size");

      if (CHANGED (uuconf_qcalled_local_size))
	uvwrite_size (e, q->uuconf_qcalled_local_size, "called-local-size");

      if (CHANGED (uuconf_qcalled_remote_size))
	uvwrite_size (e, q->uuconf_qcalled_remote_size, "called-remote-size");

      if (CHANGED (uuconf_ibaud) || CHANGED (uuconf_ihighbaud))
	{
	  if (q->uuconf_ibaud >= 0)
	    {
	      if (q->uuconf_ihighbaud > 0)
		fprintf (e, "baud-range %ld %ld\n", q->uuconf_ibaud,
			 q->uuconf_ihighbaud);
	      else
		fprintf (e, "baud %ld\n", q->uuconf_ibaud);
	    }
	}

      if (CHANGED (uuconf_zport) || CHANGED (uuconf_qport))
	{
	  if (q->uuconf_zport != NULL
	      && q->uuconf_zport != (char *) &_uuconf_unset)
	    uvwrite_string (e, q->uuconf_zport, "port");
	  else if (q->uuconf_qport != NULL
		   && (q->uuconf_qport
		       != (struct uuconf_port *) &_uuconf_unset))
	    uvwrite_taylor_port (e, q->uuconf_qport, "port ");
	}

      if (CHANGED (uuconf_zphone))
	{
	  const char *zcmd;

	  if (q->uuconf_qport != NULL
	      && q->uuconf_qport != (struct uuconf_port *) &_uuconf_unset
	      && (q->uuconf_qport->uuconf_ttype == UUCONF_PORTTYPE_TCP
		  || q->uuconf_qport->uuconf_ttype == UUCONF_PORTTYPE_TLI))
	    zcmd = "address";
	  else
	    zcmd = "phone";
	  uvwrite_string (e, q->uuconf_zphone, zcmd);
	}

      uvwrite_chat (e, &q->uuconf_schat,
		    (qlast == NULL
		     ? (struct uuconf_chat *) NULL
		     : &qlast->uuconf_schat),
		    "", TRUE);

      if (CHANGED (uuconf_zcall_login))
	uvwrite_string (e, q->uuconf_zcall_login, "call-login");

      if (CHANGED (uuconf_zcall_password))
	uvwrite_string (e, q->uuconf_zcall_password, "call-password");

      if (CHANGED (uuconf_zcalled_login))
	uvwrite_string (e, q->uuconf_zcalled_login, "called-login");

      if (CHANGED (uuconf_fcallback))
	uvwrite_boolean (e, q->uuconf_fcallback, "callback");

      if (CHANGED (uuconf_fsequence))
	uvwrite_boolean (e, q->uuconf_fsequence, "sequence");

      if (CHANGED (uuconf_zprotocols))
	uvwrite_string (e, q->uuconf_zprotocols, "protocol");

      if (CHANGED (uuconf_qproto_params))
	uvwrite_proto_params (e, q->uuconf_qproto_params, "");
      
      uvwrite_chat (e, &q->uuconf_scalled_chat,
		    (qlast == NULL
		     ? (struct uuconf_chat *) NULL
		     : &qlast->uuconf_scalled_chat),
		    "called-", FALSE);

      if (CHANGED (uuconf_zdebug))
	uvwrite_string (e, q->uuconf_zdebug, "debug");

      if (CHANGED (uuconf_zmax_remote_debug))
	uvwrite_string (e, q->uuconf_zmax_remote_debug, "max-remote-debug");

      if ((CHANGED (uuconf_fsend_request)
	   || CHANGED (uuconf_frec_request))
	  && (q->uuconf_fsend_request >= 0
	      || q->uuconf_frec_request >= 0))
	{
	  if (q->uuconf_fsend_request >= 0
	      && (q->uuconf_fsend_request > 0
		  ? q->uuconf_frec_request > 0
		  : q->uuconf_frec_request == 0))
	    uvwrite_boolean (e, q->uuconf_fsend_request, "request");
	  else
	    {
	      uvwrite_boolean (e, q->uuconf_fsend_request, "send-request");
	      uvwrite_boolean (e, q->uuconf_frec_request,
			       "receive-request");
	    }
	}

      if ((CHANGED (uuconf_fcall_transfer)
	   || CHANGED (uuconf_fcalled_transfer))
	  && (q->uuconf_fcall_transfer >= 0
	      || q->uuconf_fcalled_transfer >= 0))
	{
	  if (q->uuconf_fcall_transfer >= 0
	      && (q->uuconf_fcall_transfer > 0
		  ? q->uuconf_fcalled_transfer > 0
		  : q->uuconf_fcalled_transfer == 0))
	    uvwrite_boolean (e, q->uuconf_fcall_transfer, "transfer");
	  else
	    {
	      uvwrite_boolean (e, q->uuconf_fcall_transfer, "call-transfer");
	      uvwrite_boolean (e, q->uuconf_fcalled_transfer,
			       "called-transfer");
	    }
	}

      if (CHANGED (uuconf_pzlocal_send))
	uvwrite_string_array (e, q->uuconf_pzlocal_send, "local-send");

      if (CHANGED (uuconf_pzremote_send))
	uvwrite_string_array (e, q->uuconf_pzremote_send, "remote-send");

      if (CHANGED (uuconf_pzlocal_receive))
	uvwrite_string_array (e, q->uuconf_pzlocal_receive, "local-receive");

      if (CHANGED (uuconf_pzremote_receive))
	uvwrite_string_array (e, q->uuconf_pzremote_receive,
			      "remote-receive");

      if (CHANGED (uuconf_pzpath))
	uvwrite_string_array (e, q->uuconf_pzpath, "command-path");

      if (CHANGED (uuconf_pzcmds))
	uvwrite_string_array (e, q->uuconf_pzcmds, "commands");

      if (CHANGED (uuconf_cfree_space)
	  && q->uuconf_cfree_space >= 0)
	fprintf (e, "free-space %ld\n", q->uuconf_cfree_space);

      if (CHANGED (uuconf_pzforward_from))
	uvwrite_string_array (e, q->uuconf_pzforward_from, "forward-from");

      if (CHANGED (uuconf_pzforward_to))
	uvwrite_string_array (e, q->uuconf_pzforward_to, "forward-to");

      if (CHANGED (uuconf_zpubdir))
	uvwrite_string (e, q->uuconf_zpubdir, "pubdir");

      if (CHANGED (uuconf_zlocalname))
	uvwrite_string (e, q->uuconf_zlocalname, "myname");
    }
}

/* Write out V2 system information.  */

static void
uvwrite_v2_system (e, q)
     FILE *e;
     const struct uuconf_system *q;
{
  for (; q != NULL; q = q->uuconf_qalternate)
    {
      fprintf (e, "%s", q->uuconf_zname);

      if (q->uuconf_qtimegrade != (struct uuconf_timespan *) &_uuconf_unset)
	{
	  fprintf (e, " ");
	  uvwrite_time (e, q->uuconf_qtimegrade);

	  if (q->uuconf_zport != (char *) &_uuconf_unset
	      || q->uuconf_qport != (struct uuconf_port *) &_uuconf_unset)
	    {
	      struct uuconf_port *qp;
	      boolean ftcp;

	      qp = q->uuconf_qport;
	      ftcp = (qp != (struct uuconf_port *) &_uuconf_unset
		      && qp != NULL
		      && qp->uuconf_ttype == UUCONF_PORTTYPE_TCP);
	      if (ftcp
		  || (q->uuconf_zport != NULL
		      && q->uuconf_zport != (char *) &_uuconf_unset))
		{
		  if (ftcp)
		    fprintf (e, " TCP");
		  else
		    fprintf (e, " %s", q->uuconf_zport);

		  if (ftcp || q->uuconf_ibaud >= 0)
		    {
		      fprintf (e, " ");
		      if (ftcp)
			{
			  const char *zport;

			  zport = qp->uuconf_u.uuconf_stcp.uuconf_zport;
			  if (zport == NULL)
			    zport = "uucp";
			  fprintf (e, "%s", zport);
			}
		      else
			fprintf (e, "%ld", q->uuconf_ibaud);

		      if (q->uuconf_zphone != (char *) &_uuconf_unset
			  && q->uuconf_zphone != NULL)
			{
			  char **pzc;
			  
			  fprintf (e, " %s", q->uuconf_zphone);
			  pzc = q->uuconf_schat.uuconf_pzchat;
			  if (pzc != (char **) &_uuconf_unset
			      && pzc != NULL)
			    {
			      fprintf (e, " ");
			      uvwrite_chat_script (e, pzc);
			    }
			}
		    }
		}
	    }
	}

      fprintf (e, "\n");

      /* Here we should gather information to write out to USERFILE
	 and L.cmds, and perhaps some day we will.  It's much more
	 likely to happen if somebody else does it, though.  */
    }
}

/* Write out HDB system information.  */

static void
uvwrite_hdb_system (e, qsys)
     FILE *e;
     const struct uuconf_system *qsys;
{
  const struct uuconf_system *q;
  struct shpermissions sperm;
  char *azmachine[2];
  char *azlogname[2];

  for (q = qsys; q != NULL; q = q->uuconf_qalternate)
    {
      if (q->uuconf_fcall)
	{
	  fprintf (e, "%s", q->uuconf_zname);

	  if (q->uuconf_qtimegrade
	      != (struct uuconf_timespan *) &_uuconf_unset)
	    {
	      const char *zport;

	      fprintf (e, " ");
	      uvwrite_time (e, q->uuconf_qtimegrade);

	      zport = q->uuconf_zport;
	      if (q->uuconf_qport != NULL
		  && q->uuconf_qport != (struct uuconf_port *) &_uuconf_unset
		  && q->uuconf_qport->uuconf_ttype == UUCONF_PORTTYPE_TCP)
		zport = "TCP";
	      if (zport != NULL && zport != (char *) &_uuconf_unset)
		{
		  fprintf (e, " %s", zport);
		  if (q->uuconf_zprotocols != (char *) &_uuconf_unset
		      && q->uuconf_zprotocols != NULL)
		    fprintf (e, ",%s", q->uuconf_zprotocols);

		  if (q->uuconf_ibaud >= 0
		      || q->uuconf_zphone != (char *) &_uuconf_unset)
		    {
		      fprintf (e, " ");
		      if (q->uuconf_ibaud < 0)
			fprintf (e, "Any");
		      else
			{
			  fprintf (e, "%ld", q->uuconf_ibaud);
			  if (q->uuconf_ihighbaud >= 0)
			    fprintf (e, "-%ld", q->uuconf_ihighbaud);
			}

		      if (q->uuconf_zphone != (char *) &_uuconf_unset
			  && q->uuconf_zphone != NULL)
			{
			  char **pzc;
			  
			  fprintf (e, " %s", q->uuconf_zphone);
			  pzc = q->uuconf_schat.uuconf_pzchat;
			  if (pzc != (char **) &_uuconf_unset
			      && pzc != NULL)
			    {
			      fprintf (e, " ");
			      uvwrite_chat_script (e, pzc);
			    }
			}
		    }
		}
	    }

	  fprintf (e, "\n");
	}
    }

  /* Build a Permissions entry for this system.  There will be only
     one MACHINE entry for a given system.  */

  for (q = qsys; q != NULL; q = q->uuconf_qalternate)
    if (q->uuconf_fcall)
      break;

  if (q != NULL)
    {
      sperm.qnext = NULL;
      sperm.pzlogname = NULL;
      sperm.pzmachine = NULL;
      sperm.frequest = -1;
      sperm.fsendfiles = -1;
      sperm.pzread = NULL;
      sperm.pzwrite = NULL;
      sperm.fcallback = -1;
      sperm.pzcommands = NULL;
      sperm.pzvalidate = NULL;
      sperm.zmyname = NULL;
      sperm.zpubdir = NULL;
      sperm.pzalias = NULL;

      azmachine[0] = q->uuconf_zname;
      azmachine[1] = NULL;
      sperm.pzmachine = azmachine;
      if (q->uuconf_fsend_request >= 0)
	sperm.frequest = q->uuconf_fsend_request;
      if (q->uuconf_pzremote_send != (char **) &_uuconf_unset
	  && q->uuconf_pzremote_send != NULL)
	sperm.pzread = q->uuconf_pzremote_send;
      if (q->uuconf_pzremote_receive != (char **) &_uuconf_unset
	  && q->uuconf_pzremote_receive != NULL)
	sperm.pzwrite = q->uuconf_pzremote_receive;
      if (q->uuconf_pzcmds != (char **) &_uuconf_unset
	  && q->uuconf_pzcmds != NULL)
	sperm.pzcommands = q->uuconf_pzcmds;
      if (q->uuconf_zlocalname != (char *) &_uuconf_unset
	  && q->uuconf_zlocalname != NULL)
	sperm.zmyname = q->uuconf_zlocalname;
      if (q->uuconf_zpubdir != (char *) &_uuconf_unset
	  && q->uuconf_zpubdir != NULL)
	sperm.zpubdir = q->uuconf_zpubdir;
      if (q->uuconf_pzalias != (char **) &_uuconf_unset
	  && q->uuconf_pzalias != NULL)
	sperm.pzalias = q->uuconf_pzalias;

      if (q->uuconf_fcalled
	  && q->uuconf_zcalled_login != (char *) &_uuconf_unset
	  && q->uuconf_zcalled_login != NULL)
	{
	  azlogname[0] = q->uuconf_zcalled_login;
	  azlogname[1] = NULL;
	  sperm.pzlogname = azlogname;
	  if (q->uuconf_fcalled_transfer >= 0)
	    sperm.fsendfiles = q->uuconf_fcalled_transfer;
	  if (q->uuconf_fcallback >= 0)
	    sperm.fcallback = q->uuconf_fcallback;
	  sperm.pzvalidate = azmachine;
	}

      uvadd_perm (&sperm);
    }

  /* Now add a Permissions entry for each alternative that is not used
     for calling out.  */
  for (q = qsys; q != NULL; q = q->uuconf_qalternate)
    {
      if (! q->uuconf_fcalled || q->uuconf_fcall)
	continue;

      sperm.qnext = NULL;
      sperm.pzlogname = NULL;
      sperm.pzmachine = NULL;
      sperm.frequest = -1;
      sperm.fsendfiles = -1;
      sperm.pzread = NULL;
      sperm.pzwrite = NULL;
      sperm.fcallback = -1;
      sperm.pzcommands = NULL;
      sperm.pzvalidate = NULL;
      sperm.zmyname = NULL;
      sperm.zpubdir = NULL;
      sperm.pzalias = NULL;

      if (q->uuconf_zcalled_login != (char *) &_uuconf_unset
	  && q->uuconf_zcalled_login != NULL)
	azlogname[0] = q->uuconf_zcalled_login;
      else
	azlogname[0] = (char *) "OTHER";
      azlogname[1] = NULL;
      sperm.pzlogname = azlogname;

      if (q->uuconf_fsend_request >= 0)
	sperm.frequest = q->uuconf_fsend_request;
      if (q->uuconf_fcalled_transfer >= 0)
	sperm.fsendfiles = q->uuconf_fcalled_transfer;
      if (q->uuconf_pzremote_send != (char **) &_uuconf_unset
	  && q->uuconf_pzremote_send != NULL)
	sperm.pzread = q->uuconf_pzremote_send;
      if (q->uuconf_pzremote_receive != (char **) &_uuconf_unset
	  && q->uuconf_pzremote_receive != NULL)
	sperm.pzwrite = q->uuconf_pzremote_receive;
      if (q->uuconf_fcallback >= 0)
	sperm.fcallback = q->uuconf_fcallback;
      if (q->uuconf_zlocalname != (char *) &_uuconf_unset
	  && q->uuconf_zlocalname != NULL)
	sperm.zmyname = q->uuconf_zlocalname;
      if (q->uuconf_zpubdir != (char *) &_uuconf_unset
	  && q->uuconf_zpubdir != NULL)
	sperm.zpubdir = q->uuconf_zpubdir;

      uvadd_perm (&sperm);
    }
}

/* Compare two strings from a Permissions entry, returning TRUE if
   they are the same.  */

static boolean
fvperm_string_cmp (z1, z2)
     const char *z1;
     const char *z2;
{
  if (z1 == NULL)
    return z2 == NULL;

  if (z2 == NULL)
    return FALSE;

  return strcmp (z1, z2) == 0;
}

/* Compare two arrays of strings from a Permissions entry, returning
   TRUE if they are the same.  */

static boolean
fvperm_array_cmp (pz1, pz2)
     const char **pz1;
     const char **pz2;
{
  if (pz1 == NULL)
    return pz2 == NULL;

  if (pz2 == NULL)
    return FALSE;

  for (; *pz1 != NULL && *pz2 != NULL; pz1++, pz2++)
    if (strcmp (*pz1, *pz2) != 0)
      break;

  return *pz1 == NULL && *pz2 == NULL;
}      

/* Add a Permissions entry to a global list, combining entries where
   possible.  */

static void
uvadd_perm (qadd)
     struct shpermissions *qadd;
{
  struct shpermissions *qlook;
  struct shpermissions *qnew;
  int iret;

  /* If there's no information, don't bother to add this entry.  */
  if (qadd->pzlogname == NULL
      && qadd->frequest < 0
      && qadd->fsendfiles < 0
      && qadd->pzread == NULL
      && qadd->pzwrite == NULL
      && qadd->fcallback < 0
      && qadd->pzcommands == NULL
      && qadd->pzvalidate == NULL
      && qadd->zmyname == NULL
      && qadd->zpubdir == NULL
      && qadd->pzalias == NULL)
    return;

  for (qlook = qVperms; qlook != NULL; qlook = qlook->qnext)
    {
      /* See if we can merge qadd into qlook.  */
      if (qadd->pzlogname == NULL
	  ? qlook->pzlogname != NULL
	  : qlook->pzlogname == NULL)
	continue;
      if (qadd->pzmachine == NULL
	  ? qlook->pzmachine != NULL
	  : qlook->pzmachine == NULL)
	continue;
      if (qadd->frequest != qlook->frequest
	  || qadd->fsendfiles != qlook->fsendfiles
	  || qadd->fcallback != qlook->fcallback)
	continue;
      if (! fvperm_string_cmp (qadd->zmyname, qlook->zmyname)
	  || ! fvperm_string_cmp (qadd->zpubdir, qlook->zpubdir))
	continue;
      if (! fvperm_array_cmp ((const char **) qadd->pzread,
			      (const char **) qlook->pzread)
	  || ! fvperm_array_cmp ((const char **)  qadd->pzwrite,
				 (const char **) qlook->pzwrite)
	  || ! fvperm_array_cmp ((const char **) qadd->pzcommands,
				 (const char **) qlook->pzcommands))
	continue;

      /* Merge qadd into qlook.  */
      if (qadd->pzmachine != NULL)
	{
	  iret = _uuconf_iadd_string ((struct sglobal *) NULL,
				      qadd->pzmachine[0], FALSE,
				      TRUE, &qlook->pzmachine,
				      (pointer) NULL);
	  if (iret != UUCONF_SUCCESS)
	    uvuuconf_error ((pointer) NULL, iret);
	}
      if (qadd->pzlogname != NULL)
	{
	  iret = _uuconf_iadd_string ((struct sglobal *) NULL,
				      qadd->pzlogname[0], FALSE,
				      TRUE, &qlook->pzlogname,
				      (pointer) NULL);
	  if (iret != UUCONF_SUCCESS)
	    uvuuconf_error ((pointer) NULL, iret);
	}
      if (qadd->pzalias != NULL)
	{
	  char **pz;

	  for (pz = qadd->pzalias; *pz != NULL; pz++)
	    {
	      iret = _uuconf_iadd_string ((struct sglobal *) NULL,
					  *pz, FALSE, TRUE,
					  &qlook->pzalias, (pointer) NULL);
	      if (iret != UUCONF_SUCCESS)
		uvuuconf_error ((pointer) NULL, iret);
	    }
	}

      return;
    }

  /* We must add qadd as a new entry on the list, which means we must
     copy it into the heap.  */

  qnew = (struct shpermissions *) malloc (sizeof (struct shpermissions));
  if (qnew == NULL)
    uvuuconf_error ((pointer) NULL, UUCONF_MALLOC_FAILED);
  *qnew = *qadd;
  if (qadd->pzmachine != NULL)
    {
      qnew->pzmachine = NULL;
      iret = _uuconf_iadd_string ((struct sglobal *) NULL,
				  qadd->pzmachine[0], FALSE,
				  FALSE, &qnew->pzmachine,
				  (pointer) NULL);
      if (iret != UUCONF_SUCCESS)
	uvuuconf_error ((pointer) NULL, iret);
    }
  if (qadd->pzlogname != NULL)
    {
      qnew->pzlogname = NULL;
      iret = _uuconf_iadd_string ((struct sglobal *) NULL,
				  qadd->pzlogname[0], FALSE,
				  FALSE, &qnew->pzlogname,
				  (pointer) NULL);
      if (iret != UUCONF_SUCCESS)
	uvuuconf_error ((pointer) NULL, iret);
    }
  if (qadd->pzvalidate != NULL)
    qnew->pzvalidate = qnew->pzmachine;

  qnew->qnext = qVperms;
  qVperms = qnew;
}

/* Write out the Permissions entries.  */

static void
uvwrite_perms ()
{
  char ab[sizeof ZCURDIR + sizeof HDB_PERMISSIONS - 1];
  FILE *e;
  struct shpermissions *q;

  sprintf (ab, "%s%s", ZCURDIR, HDB_PERMISSIONS);
  e = fopen (ab, "w");
  if (e == NULL)
    {
      fprintf (stderr, "uuchk:%s: ", ab);
      perror ("fopen");
      exit (EXIT_FAILURE);
    }

  fprintf (e, "# Permissions file automatically generated by uuconv.\n");

  for (q = qVperms; q != NULL; q = q->qnext)
    {
      size_t ccol;

      ccol = 0;
      uvwrite_perm_array (e, (const char **) q->pzlogname, "LOGNAME", &ccol);
      uvwrite_perm_array (e, (const char **) q->pzmachine, "MACHINE", &ccol);
      uvwrite_perm_boolean (e, q->frequest, "REQUEST", &ccol, FALSE);
      uvwrite_perm_boolean (e, q->fsendfiles, "SENDFILES", &ccol, TRUE);
      uvwrite_perm_rw_array (e, (const char **) q->pzread, "READ", &ccol);
      uvwrite_perm_rw_array (e, (const char **) q->pzwrite, "WRITE", &ccol);
      uvwrite_perm_boolean (e, q->fcallback, "CALLBACK", &ccol, FALSE);
      uvwrite_perm_array (e, (const char **) q->pzcommands, "COMMANDS",
			  &ccol);
      uvwrite_perm_array (e, (const char **) q->pzvalidate, "VALIDATE",
			  &ccol);
      uvwrite_perm_string (e, q->zmyname, "MYNAME", &ccol);
      uvwrite_perm_string (e, q->zpubdir, "PUBDIR", &ccol);
      uvwrite_perm_array (e, (const char **) q->pzalias, "ALIAS", &ccol);

      fprintf (e, "\n");
    }

  if (ferror (e)
      || fclose (e) == EOF)
    {
      fprintf (stderr, "uuchk:%s: error during output\n", HDB_PERMISSIONS);
      exit (EXIT_FAILURE);
    }
}

/* Write an array out to the Permissions file.  */

static void
uvwrite_perm_array (e, pzarg, zcmd, pccol)
     FILE *e;
     const char **pzarg;
     const char *zcmd;
     size_t *pccol;
{
  size_t c;
  const char **pz;

  if (pzarg == NULL)
    return;

  c = strlen (zcmd) + 1;
  
  for (pz = pzarg; *pz != NULL; pz++)
    c += strlen (*pz) + 1;

  if (*pccol > 20 && c + *pccol > 75)
    {
      fprintf (e, " \\\n");
      *pccol = c - 1;
    }
  else
    {
      if (*pccol != 0)
	fprintf (e, " ");
      *pccol += c;
    }

  fprintf (e, "%s=", zcmd);
  for (pz = pzarg; *pz != NULL; pz++)
    {
      if (pz != pzarg)
	fprintf (e, ":");
      fprintf (e, "%s", *pz);
    }
}

/* Write a boolean value out to the Permissions file.  This may be
   either a yes/no boolean or a yes/call boolean (the latter is for
   SENDFILES).  */

static void
uvwrite_perm_boolean (e, f, zcmd, pccol, fsendfiles)
     FILE *e;
     int f;
     const char *zcmd;
     size_t *pccol;
     boolean fsendfiles;
{
  const char *az[2];

  if (f < 0)
    return;

  if (f)
    az[0] = "yes";
  else
    az[0] = fsendfiles ? "call" : "no";
  az[1] = NULL;

  uvwrite_perm_array (e, az, zcmd, pccol);
}

/* Write a set of READ or WRITE entries to the Permissions file.  We
   have to separate out all entries that start with '!'.  */

static void
uvwrite_perm_rw_array (e, pzarg, zcmd, pccol)
     FILE *e;
     const char **pzarg;
     const char *zcmd;
     size_t *pccol;
{
  size_t c;
  const char **pz, **pzcopy, **pzset;

  if (pzarg == NULL)
    return;

  c = 0;
  for (pz = pzarg; *pz != NULL; pz++)
    c++;

  pzcopy = (const char **) malloc ((c + 1) * sizeof (char *));
  if (pzcopy == NULL)
    uvuuconf_error ((pointer) NULL, UUCONF_MALLOC_FAILED);

  pzset = pzcopy;
  for (pz = pzarg; *pz != NULL; pz++)
    if ((*pz)[0] != '!')
      *pzset++ = *pz;
  *pzset = NULL;

  if (pzset != pzcopy)
    uvwrite_perm_array (e, (const char **) pzcopy, zcmd, pccol);

  pzset = pzcopy;
  for (pz = pzarg; *pz != NULL; pz++)
    if ((*pz)[0] == '!')
      *pzset++ = *pz;
  *pzset = NULL;

  if (pzset != pzcopy)
    {
      char ab[20];

      sprintf (ab, "NO%s", zcmd);
      uvwrite_perm_array (e, (const char **) pzcopy, ab, pccol);
    }
}

/* Write a string out to the Permissions file.  */

static void
uvwrite_perm_string (e, z, zcmd, pccol)
     FILE *e;
     const char *z;
     const char *zcmd;
     size_t *pccol;
{
  const char *az[2];

  if (z == NULL)
    return;

  az[0] = z;
  az[1] = NULL;

  uvwrite_perm_array (e, az, zcmd, pccol);
}

/* Write out a Taylor UUCP port.  This is called via uuconf_find_port;
   the pinfo argument is the port file.  */

static int
ivwrite_taylor_port (qport, pinfo)
     struct uuconf_port *qport;
     pointer pinfo;
{
  FILE *e = (FILE *) pinfo;

  fprintf (e, "port %s\n", qport->uuconf_zname);

  uvwrite_taylor_port (e, qport, "");

  /* Return UUCONF_NOT_FOUND to force uuconf_find_port to keep looking
     for ports.  */
  return UUCONF_NOT_FOUND;
}

/* Write a port out to a Taylor UUCP configuration file.  This doesn't
   output the name, since it is called to output a specially defined
   port in the sys file.  */

static void
uvwrite_taylor_port (e, qport, zprefix)
     FILE *e;
     struct uuconf_port *qport;
     const char *zprefix;
{
  const char *ztype;
  char ab[100];

  switch (qport->uuconf_ttype)
    {
    default:
    case UUCONF_PORTTYPE_UNKNOWN:
      fprintf (stderr, "uuconv: Bad port type\n");
      exit (EXIT_FAILURE);
      break;
    case UUCONF_PORTTYPE_STDIN:
      ztype = "stdin";
      break;
    case UUCONF_PORTTYPE_MODEM:
      ztype = "modem";
      break;
    case UUCONF_PORTTYPE_DIRECT:
      ztype = "direct";
      break;
    case UUCONF_PORTTYPE_TCP:
      ztype = "tcp";
      break;
    case UUCONF_PORTTYPE_TLI:
      ztype = "tli";
      break;
    case UUCONF_PORTTYPE_PIPE:
      ztype = "pipe";
      break;
    }

  fprintf (e, "%stype %s\n", zprefix, ztype);

  if (qport->uuconf_zprotocols != NULL)
    fprintf (e, "%sprotocol %s\n", zprefix, qport->uuconf_zprotocols);

  if (qport->uuconf_qproto_params != NULL)
    uvwrite_proto_params (e, qport->uuconf_qproto_params, zprefix);

  if ((qport->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
    {
      sprintf (ab, "%sseven-bit", zprefix);
      uvwrite_boolean (e,
		       ((qport->uuconf_ireliable & UUCONF_RELIABLE_EIGHT)
			== 0),
		       ab);
      sprintf (ab, "%sreliable", zprefix);
      uvwrite_boolean (e,
		       ((qport->uuconf_ireliable & UUCONF_RELIABLE_RELIABLE)
			!= 0),
		       ab);
      sprintf (ab, "%shalf-duplex", zprefix);
      uvwrite_boolean (e,
		       ((qport->uuconf_ireliable & UUCONF_RELIABLE_FULLDUPLEX)
			== 0),
		       ab);
    }

  if (qport->uuconf_zlockname != NULL)
    fprintf (e, "%slockname %s\n", zprefix, qport->uuconf_zlockname);

  switch (qport->uuconf_ttype)
    {
    default:
      break;
    case UUCONF_PORTTYPE_MODEM:
      {
	struct uuconf_modem_port *qm;

	qm = &qport->uuconf_u.uuconf_smodem;
	if (qm->uuconf_zdevice != NULL)
	  fprintf (e, "%sdevice %s\n", zprefix, qm->uuconf_zdevice);
	if (qm->uuconf_zdial_device != NULL)
	  fprintf (e, "%sdial-device %s\n", zprefix, qm->uuconf_zdial_device);
	if (qm->uuconf_ibaud != 0)
	  fprintf (e, "%sbaud %ld\n", zprefix, qm->uuconf_ibaud);
	if (qm->uuconf_ilowbaud != 0)
	  fprintf (e, "%sbaud-range %ld %ld\n", zprefix, qm->uuconf_ilowbaud,
		   qm->uuconf_ihighbaud);
	if (! qm->uuconf_fcarrier)
	  fprintf (e, "%scarrier false\n", zprefix);
	if (! qm->uuconf_fhardflow)
	  fprintf (e, "%shardflow false\n", zprefix);
	if (qm->uuconf_pzdialer != NULL)
	  {
	    if (qm->uuconf_pzdialer[1] == NULL)
	      fprintf (e, "%sdialer %s\n", zprefix, qm->uuconf_pzdialer[0]);
	    else
	      {
		sprintf (ab, "%sdialer-sequence", zprefix);
		uvwrite_string_array (e, qm->uuconf_pzdialer, ab);
	      }
	  }
	if (qm->uuconf_qdialer != NULL)
	  {
	    sprintf (ab, "%sdialer ", zprefix);
	    uvwrite_taylor_dialer (e, qm->uuconf_qdialer, ab);
	  }
      }
      break;
    case UUCONF_PORTTYPE_DIRECT:
      {
	struct uuconf_direct_port *qd;

	qd = &qport->uuconf_u.uuconf_sdirect;
	if (qd->uuconf_zdevice != NULL)
	  fprintf (e, "%sdevice %s\n", zprefix, qd->uuconf_zdevice);
	if (qd->uuconf_ibaud != 0)
	  fprintf (e, "%sbaud %ld\n", zprefix, qd->uuconf_ibaud);
	if (qd->uuconf_fcarrier)
	  fprintf (e, "%scarrier true\n", zprefix);
	if (! qd->uuconf_fhardflow)
	  fprintf (e, "%shardflow false\n", zprefix);
      }
      break;
    case UUCONF_PORTTYPE_TCP:
      if (qport->uuconf_u.uuconf_stcp.uuconf_zport != NULL)
	fprintf (e, "%sservice %s\n", zprefix,
		 qport->uuconf_u.uuconf_stcp.uuconf_zport);
      if (qport->uuconf_u.uuconf_stcp.uuconf_pzdialer != NULL)
	{
	  sprintf (ab, "%sdialer-sequence", zprefix);
	  uvwrite_string_array (e,
				qport->uuconf_u.uuconf_stcp.uuconf_pzdialer,
				ab);
	}
      break;
    case UUCONF_PORTTYPE_TLI:
      {
	struct uuconf_tli_port *qt;

	qt = &qport->uuconf_u.uuconf_stli;
	if (qt->uuconf_zdevice != NULL)
	  fprintf (e, "%sdevice %s\n", zprefix, qt->uuconf_zdevice);
	sprintf (ab, "%sstream", zprefix);
	uvwrite_boolean (e, qt->uuconf_fstream, ab);
	if (qt->uuconf_pzpush != NULL)
	  {
	    sprintf (ab, "%spush", zprefix);
	    uvwrite_string_array (e, qt->uuconf_pzpush, ab);
	  }
	if (qt->uuconf_pzdialer != NULL)
	  {
	    sprintf (ab, "%sdialer-sequence", zprefix);
	    uvwrite_string_array (e, qt->uuconf_pzdialer, ab);
	  }
	if (qt->uuconf_zservaddr != NULL)
	  fprintf (e, "%sserver-address %s\n", zprefix,
		   qt->uuconf_zservaddr);
      }
      break;
    case UUCONF_PORTTYPE_PIPE:
      {
	struct uuconf_pipe_port *qp;

	qp = &qport->uuconf_u.uuconf_spipe;
	if (qp->uuconf_pzcmd != NULL)
	  {
	    sprintf (ab, "%scommad", zprefix);
	    uvwrite_string_array (e, qp->uuconf_pzcmd, ab);
	  }
      }
      break;
    }
}

/* Write out a port to the V2 L-devices file.  This is called via
   uuconf_find_port.  */

static int
ivwrite_v2_port (qport, pinfo)
     struct uuconf_port *qport;
     pointer pinfo;
{
  FILE *e = (FILE *) pinfo;

  if (qport->uuconf_ttype == UUCONF_PORTTYPE_DIRECT)
    {
      fprintf (e, "DIR %s - %ld direct",
	       qport->uuconf_u.uuconf_sdirect.uuconf_zdevice,
	       qport->uuconf_u.uuconf_sdirect.uuconf_ibaud);
    }
  else if (qport->uuconf_ttype == UUCONF_PORTTYPE_MODEM)
    {
      fprintf (e, "%s %s ", qport->uuconf_zname,
	       qport->uuconf_u.uuconf_smodem.uuconf_zdevice);
      if (qport->uuconf_u.uuconf_smodem.uuconf_zdial_device != NULL)
	fprintf (e, "%s", qport->uuconf_u.uuconf_smodem.uuconf_zdial_device);
      else
	fprintf (e, "-");
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_smodem.uuconf_ilowbaud != 0L)
	fprintf (e, "%ld-%ld",
		 qport->uuconf_u.uuconf_smodem.uuconf_ilowbaud,
		 qport->uuconf_u.uuconf_smodem.uuconf_ihighbaud);
      else if (qport->uuconf_u.uuconf_smodem.uuconf_ibaud != 0L)
	fprintf (e, "%ld", qport->uuconf_u.uuconf_smodem.uuconf_ibaud);
      else
	fprintf (e, "Any");
      if (qport->uuconf_u.uuconf_smodem.uuconf_pzdialer != NULL)
	fprintf (e, " %s",
		 qport->uuconf_u.uuconf_smodem.uuconf_pzdialer[0]);
    }
  else
    {
      fprintf (e, "# Ignoring port %s with unsupported type",
	       qport->uuconf_zname);
    }

  fprintf (e, "\n");

  /* Return UUCONF_NOT_FOUND to force uuconf_find_port to keep looking
     for a port.  */
  return UUCONF_NOT_FOUND;
}

/* Write out a port to the HDB Devices file.  This is called via
   uuconf_find_port.  */

static int
ivwrite_hdb_port (qport, pinfo)
     struct uuconf_port *qport;
     pointer pinfo;
{
  FILE *e = (FILE *) pinfo;

  if (qport->uuconf_ttype == UUCONF_PORTTYPE_DIRECT)
    {
      fprintf (e, "Direct");
      if (qport->uuconf_zprotocols != NULL)
	fprintf (e, ",%s", qport->uuconf_zprotocols);
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_sdirect.uuconf_zdevice != NULL)
	fprintf (e, "%s", qport->uuconf_u.uuconf_sdirect.uuconf_zdevice);
      else
	fprintf (e, "%s", qport->uuconf_zname);
      fprintf (e, " - %ld", qport->uuconf_u.uuconf_sdirect.uuconf_ibaud);
    }
  else if (qport->uuconf_ttype == UUCONF_PORTTYPE_MODEM)
    {
      fprintf (e, "%s", qport->uuconf_zname);
      if (qport->uuconf_zprotocols != NULL)
	fprintf (e, ",%s", qport->uuconf_zprotocols);
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_smodem.uuconf_zdevice != NULL)
	fprintf (e, "%s", qport->uuconf_u.uuconf_smodem.uuconf_zdevice);
      else
	fprintf (e, "%s", qport->uuconf_zname);
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_smodem.uuconf_zdial_device != NULL)
	fprintf (e, "%s", qport->uuconf_u.uuconf_smodem.uuconf_zdial_device);
      else
	fprintf (e, "-");
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_smodem.uuconf_ilowbaud != 0L)
	fprintf (e, "%ld-%ld",
		 qport->uuconf_u.uuconf_smodem.uuconf_ilowbaud,
		 qport->uuconf_u.uuconf_smodem.uuconf_ihighbaud);
      else if (qport->uuconf_u.uuconf_smodem.uuconf_ibaud != 0L)
	fprintf (e, "%ld", qport->uuconf_u.uuconf_smodem.uuconf_ibaud);
      else
	fprintf (e, "Any");
      if (qport->uuconf_u.uuconf_smodem.uuconf_pzdialer != NULL)
	{
	  char **pz;

	  for (pz = qport->uuconf_u.uuconf_smodem.uuconf_pzdialer;
	       *pz != NULL;
	       pz++)
	    fprintf (e, " %s", *pz);
	}
    }
  else if (qport->uuconf_ttype == UUCONF_PORTTYPE_TCP)
    {
      char **pz;

      fprintf (e, "TCP");
      if (qport->uuconf_zprotocols != NULL)
	fprintf (e, ",%s", qport->uuconf_zprotocols);
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_stcp.uuconf_zport == NULL)
	fprintf (e, "uucp");
      else
	fprintf (e, "%s", qport->uuconf_u.uuconf_stcp.uuconf_zport);
      fprintf (e, " - -");
      pz = qport->uuconf_u.uuconf_stcp.uuconf_pzdialer;
      if (pz != NULL)
	for (; *pz != NULL; pz++)
	  fprintf (e, " %s", *pz);
    }
  else if (qport->uuconf_ttype == UUCONF_PORTTYPE_TLI)
    {
      char **pz;

      fprintf (e, "%s", qport->uuconf_zname);
      if (qport->uuconf_zprotocols != NULL)
	fprintf (e, ",%s", qport->uuconf_zprotocols);
      fprintf (e, " ");
      if (qport->uuconf_u.uuconf_stli.uuconf_zdevice != NULL)
	fprintf (e, "%s", qport->uuconf_u.uuconf_smodem.uuconf_zdevice);
      else
	fprintf (e, "-");
      fprintf (e, " - -");
      pz = qport->uuconf_u.uuconf_stli.uuconf_pzdialer;
      if (pz == NULL
	  || *pz == NULL
	  || (strcmp (*pz, "TLI") != 0
	      && strcmp (*pz, "TLIS") != 0))
	fprintf (e, " TLI%s \\D",
		 qport->uuconf_u.uuconf_stli.uuconf_fstream ? "S" : "");
      if (pz != NULL)
	for (; *pz != NULL; pz++)
	  fprintf (e, " %s", *pz);
    }
  else
    {
      fprintf (e, "# Ignoring port %s with unsupported type",
	       qport->uuconf_zname);
    }

  fprintf (e, "\n");

  /* Return UUCONF_NOT_FOUND to force uuconf_find_port to keep looking
     for a port.  */
  return UUCONF_NOT_FOUND;
}

/* Write a dialer out to a Taylor UUCP configuration file.  This
   doesn't output the name, since it is called to output a specially
   defined dialer in the sys or port file.  */

static void
uvwrite_taylor_dialer (e, qdialer, zprefix)
     FILE *e;
     struct uuconf_dialer *qdialer;
     const char *zprefix;
{
  char ab[100];

  /* Reset default values, so we don't output them unnecessarily.  */
  if (qdialer->uuconf_schat.uuconf_ctimeout == 60)
    qdialer->uuconf_schat.uuconf_ctimeout = -1;
  if (qdialer->uuconf_schat.uuconf_fstrip)
    qdialer->uuconf_schat.uuconf_fstrip = -1;
  if (qdialer->uuconf_scomplete.uuconf_ctimeout == 60)
    qdialer->uuconf_scomplete.uuconf_ctimeout = -1;
  if (qdialer->uuconf_scomplete.uuconf_fstrip)
    qdialer->uuconf_scomplete.uuconf_fstrip = -1;
  if (qdialer->uuconf_sabort.uuconf_ctimeout == 60)
    qdialer->uuconf_sabort.uuconf_ctimeout = -1;
  if (qdialer->uuconf_sabort.uuconf_fstrip)
    qdialer->uuconf_sabort.uuconf_fstrip = -1;
  
  uvwrite_chat (e, &qdialer->uuconf_schat, (struct uuconf_chat *) NULL,
		zprefix, FALSE);
  if (qdialer->uuconf_zdialtone != NULL
      && strcmp (qdialer->uuconf_zdialtone, ",") != 0)
    fprintf (e, "%sdialtone %s\n", zprefix, qdialer->uuconf_zdialtone);
  if (qdialer->uuconf_zpause != NULL
      && strcmp (qdialer->uuconf_zpause, ",") != 0)
    fprintf (e, "%spause %s\n", zprefix, qdialer->uuconf_zpause);
  if (! qdialer->uuconf_fcarrier)
    fprintf (e, "%scarrier false\n", zprefix);
  if (qdialer->uuconf_ccarrier_wait != 60)
    fprintf (e, "%scarrier-wait %d\n", zprefix,
	     qdialer->uuconf_ccarrier_wait);
  if (qdialer->uuconf_fdtr_toggle)
    fprintf (e, "%sdtr-toggle %s %s\n", zprefix,
	     qdialer->uuconf_fdtr_toggle ? "true" : "false",
	     qdialer->uuconf_fdtr_toggle_wait ? "true" : "false");
  sprintf (ab, "%scomplete-", zprefix);
  uvwrite_chat (e, &qdialer->uuconf_scomplete, (struct uuconf_chat *) NULL,
		ab, FALSE);
  sprintf (ab, "%sabort-", zprefix);
  uvwrite_chat (e, &qdialer->uuconf_sabort, (struct uuconf_chat *) NULL,
		ab, FALSE);
  if (qdialer->uuconf_qproto_params != NULL)
    uvwrite_proto_params (e, qdialer->uuconf_qproto_params, zprefix);
  if ((qdialer->uuconf_ireliable & UUCONF_RELIABLE_SPECIFIED) != 0)
    {
      sprintf (ab, "%sseven-bit", zprefix);
      uvwrite_boolean (e,
		       ((qdialer->uuconf_ireliable & UUCONF_RELIABLE_EIGHT)
			== 0),
		       ab);
      sprintf (ab, "%sreliable", zprefix);
      uvwrite_boolean (e,
		       ((qdialer->uuconf_ireliable & UUCONF_RELIABLE_RELIABLE)
			!= 0),
		       ab);
      sprintf (ab, "%shalf-duplex", zprefix);
      uvwrite_boolean (e,
		       ((qdialer->uuconf_ireliable
			 & UUCONF_RELIABLE_FULLDUPLEX) == 0),
		       ab);
    }
}

/* Write a dialer out to an HDB configuration file.  */

static void
uvwrite_hdb_dialer (e, qdialer)
     FILE *e;
     struct uuconf_dialer *qdialer;
{
  fprintf (e, "%s ", qdialer->uuconf_zname);

  if (qdialer->uuconf_zdialtone != NULL)
    fprintf (e, "=%c", qdialer->uuconf_zdialtone[0]);
  if (qdialer->uuconf_zpause != NULL)
    fprintf (e, "-%c", qdialer->uuconf_zpause[0]);

  if (qdialer->uuconf_schat.uuconf_pzchat != NULL)
    {
      if (qdialer->uuconf_zdialtone == NULL
	  && qdialer->uuconf_zpause == NULL)
	fprintf (e, "\"\"");
      fprintf (e, " ");
      uvwrite_chat_script (e, qdialer->uuconf_schat.uuconf_pzchat);
    }

  fprintf (e, "\n");
}

/* Display a uuconf error and exit.  */

static void
uvuuconf_error (puuconf, iret)
     pointer puuconf;
     int iret;
{
  char ab[512];

  (void) uuconf_error_string (puuconf, iret, ab, sizeof ab);
  if ((iret & UUCONF_ERROR_FILENAME) == 0)
    fprintf (stderr, "uuconv: %s\n", ab);
  else
    fprintf (stderr, "uuconv:%s\n", ab);
  if (UUCONF_ERROR_VALUE (iret) != UUCONF_FOPEN_FAILED)
    exit (EXIT_FAILURE);
}
