chiark / gitweb /
REORG Delete everything that's not innduct or build system or changed for innduct
[inn-innduct.git] / backends / actsync.c
diff --git a/backends/actsync.c b/backends/actsync.c
deleted file mode 100644 (file)
index 41c35e0..0000000
+++ /dev/null
@@ -1,2766 +0,0 @@
-/* @(#) $Id: actsync.c 6372 2003-05-31 19:48:28Z rra $ */
-/* @(#) Under RCS control in /usr/local/news/src/inn/local/RCS/actsync.c,v */
-/*
- * actsync - sync or merge two active files
- *
- * usage:
- *    actsync [-b hostid][-d hostid][-g max][-i ignore_file][-I][-k][-l hostid]
- *           [-m][-n name][-o fmt][-p %][-q hostid][-s size]
- *           [-t hostid][-T][-v verbose_lvl][-z sec]
- *           [host1] host2
- *
- *      -A              use authentication to server
- *     -b hostid       ignore *.bork.bork.bork groups from:      (def: -b 0)
- *                         0   from neither host
- *                         1   from host1
- *                         2   from host2
- *                         12  from host1 and host2
- *                         21  from host1 and host2
- *     -d hostid       ignore groups with all numeric components (def: -d 0)
- *     -g max          ignore group >max levels (0=dont ignore)  (def: -g 0)
- *     -i ignore_file  file with list/types of groups to ignore  (def: no file)
- *     -I hostid       ignore_file applies only to hostid        (def: -I 12)
- *     -k              keep host1 groups with errors             (def: remove)
- *     -l hostid       flag =group problems as errors            (def: -l 12)
- *     -m              merge, keep group not on host2            (def: sync)
- *     -n name         name given to ctlinnd newgroup commands   (def: actsync)
- *     -o fmt          type of output:                           (def: -o c)
- *                         a   output groups in active format
- *                         a1  like 'a', but output ignored non-err host1 grps
- *                         ak  like 'a', keep host2 hi/low values on new groups
- *                         aK  like 'a', use host2 hi/low values always
- *                         c   output in ctlinnd change commands
- *                         x   no output, safely exec ctlinnd commands
- *                         xi  no output, safely exec commands interactively
- *     -p %            min % host1 lines unchanged allowed       (def: -p 96)
- *     -q hostid       silence errors from a host (see -b)       (def: -q 0)
- *     -s size         ignore names longer than size (0=no lim)  (def: -s 0)
- *     -t hostid       ignore bad top level groups from:(see -b) (def: -t 2)
- *     -T              no new hierarchies                        (def: allow)
- *     -v verbose_lvl  verbosity level                           (def: -v 0)
- *                         0   no debug or status reports
- *                         1   summary if work done
- *                         2   summary & actions (if exec output) only if done
- *                         3   summary & actions (if exec output)
- *                         4   debug output plus all -v 3 messages
- *     -z sec          sleep sec seconds per exec if -o x        (def: -z 4)
- *     host1           host to be changed                  (def: local server)
- *     host2           reference host used in merge
- */
-/* 
- * By: Landon Curt Noll        chongo@toad.com         (chongo was here /\../\)
- *
- * Copyright (c) Landon Curt Noll, 1996.
- * All rights reserved.
- *
- * Permission to use and modify is hereby granted so long as this 
- * notice remains.  Use at your own risk.  No warranty is implied.
- */
-
-#include "config.h"
-#include "clibrary.h"
-#include "portable/wait.h"
-#include <ctype.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <math.h>
-#include <sys/stat.h>
-#include <signal.h>
-
-#include "inn/innconf.h"
-#include "inn/messages.h"
-#include "inn/qio.h"
-#include "libinn.h"
-#include "paths.h"
-
-static const char usage[] = "\
-Usage: actsync [-A][-b hostid][-d hostid][-i ignore_file][-I hostid][-k]\n\
-        [-l hostid][-m][-n name][-o fmt][-p min_%_unchg][-q hostid]\n\
-        [-s size][-t hostid][-T][-v verbose_lvl][-z sec]\n\
-        [host1] host2\n\
-\n\
-    -A          use authentication to server\n\
-    -b hostid   ignore *.bork.bork.bork groups from:    (def: -b 0)\n\
-                0       from neither host\n\
-                1       from host1\n\
-                2       from host2\n\
-                12      from host1 and host2\n\
-                21      from host1 and host2\n\
-    -d hostid   ignore grps with all numeric components (def: -d 0)\n\
-    -g max      ignore group >max levels (0=don't)      (def: -g 0)\n\
-    -i file     file with groups to ignore              (def: no file)\n\
-    -I hostid   ignore_file applies only to hostid      (def: -I 12)\n\
-    -k          keep host1 groups with errors           (def: remove)\n\
-    -l hostid   flag =group problems as errors          (def: -l 12)\n\
-    -m          merge, keep group not on host2          (def: sync)\n\
-    -n name     name given to ctlinnd newgroup cmds     (def: actsync)\n\
-    -o fmt      type of output:                         (def: -o c)\n\
-                a       output groups in active format\n\
-                a1      like 'a', but output ignored non-err host1 grps\n\
-                ak      like 'a', keep host2 hi/low values on new groups\n\
-                aK      like 'a', use host2 hi/low values always\n\
-                c       output in ctlinnd change commands\n\
-                x       no output, safely exec ctlinnd commands\n\
-                xi      no output, safely exec commands interactively\n\
-    -p %        min % host1 lines unchanged allowed     (def: -p 96)\n\
-    -q hostid   silence errors from a host (see -b)     (def: -q 0)\n\
-    -s size     ignore names > than size (0=no lim)     (def: -s 0)\n\
-    -t hostid   ignore bad top level grps from: (see -b)(def: -t 2)\n\
-    -T          no new hierarchies                      (def: allow)\n\
-    -v level    verbosity level                         (def: -v 0)\n\
-                0       no debug or status reports\n\
-                1       summary if work done\n\
-                2       summary & actions (if exec output) only if done\n\
-                3       summary & actions (if exec output)\n\
-                4       debug output plus all -v 3 messages\n\
-    -z sec      sleep sec seconds per exec if -o x      (def: -z 4)\n\
-\n\
-    host1       host to be changed                      (def: local server)\n\
-    host2       reference host used in merge\n";
-
-
-/*
- * pat - internal ignore/check pattern
- *
- * A pattern, derived from an ignore file, will determine if a group
- * is will be checked if it is on both hosts or ignored altogether.
- *
- * The type related to the 4th field of an active file.  Types may
- * currently be one of [ymjnx=].  If '=' is one of the types, an
- * optional equivalence pattern may be given in the 'epat' element.
- *
- * For example, to ignore "foo.bar.*", if it is junked or equated to
- * a group of the form "alt.*.foo.bar.*":
- *
- *     x.pat = "foo.bar.*";
- *     x.type = "j=";
- *     x.epat = "alt.*.foo.bar.*";
- *     x.ignore = 1;
- *
- * To further check "foo.bar.mod" if it is moderated:
- *
- *     x.pat = "foo.bar.mod";
- *     x.type = "m";
- *     x.epat = NULL;
- *     x.ignore = 0;
- *
- * The 'i' value means ignore, 'c' value means 'compare'.   The last pattern
- * that matches a group determines the fate of the group.  By default all
- * groups are included.
- */
-struct pat {
-    char *pat;         /* newsgroup pattern */
-    int type_match;    /* 1 => match only if group type matches */
-    int y_type;                /* 1 => match if a 'y' type group */
-    int m_type;                /* 1 => match if a 'm' type group */
-    int n_type;                /* 1 => match if a 'n' type group */
-    int j_type;                /* 1 => match if a 'j' type group */
-    int x_type;                /* 1 => match if a 'x' type group */
-    int eq_type;       /* 1 => match if a 'eq' type group */
-    char *epat;                /* =pattern to match, if non-NULL and = is in type */
-    int ignore;                /* 0 => check matching group, 1 => ignore it */
-};
-
-/* internal representation of an active line */
-struct grp {
-    int ignore;                /* ignore reason, 0 => not ignore (see below) */
-    int hostid;                /* HOSTID this group is from */
-    int linenum;       /* >0 => active line number, <=0 => not a line */
-    int output;                /* 1 => output to produce the merged active file */
-    int remove;                /* 1 => remove this group */
-    char *name;                /* newsgroup name */
-    char *hi;          /* high article string */
-    char *low;         /* low article string */
-    char *type;                /* newsgroup type string */
-    char *outhi;       /* output high article string */
-    char *outlow;      /* output low article string */
-    char *outtype;     /* output newsgroup type string */
-};
-
-/* structure used in the process of looking for =group type problems */
-struct eqgrp {
-    int skip;          /* 1 => skip this entry */
-    struct grp *g;     /* =group that is being examined */
-    char *eq;          /* current equivalence name */
-};
-
-/*
- * These ignore reasons are listed in order severity; from mild to severe.
- */
-#define NOT_IGNORED    0x0000  /* newsgroup has not been ignored */
-#define CHECK_IGNORE   0x0001  /* ignore file ignores this entry */
-#define CHECK_TYPE     0x0002  /* group type is ignored */
-#define CHECK_BORK     0x0004  /* group is a *.bork.bork.bork group */
-#define CHECK_HIER     0x0008  /* -T && new group's hierarchy does not exist */
-#define ERROR_LONGLOOP 0x0010  /* =name refers to long =grp chain or cycle */
-#define ERROR_EQLOOP   0x0020  /* =name refers to itself in some way */
-#define ERROR_NONEQ    0x0040  /* =name does not refer to a valid group */
-#define ERROR_DUP      0x0080  /* newsgroup is a duplicate of another */
-#define ERROR_EQNAME   0x0100  /* =name is a bad group name */
-#define ERROR_BADTYPE  0x0200  /* newsgroup type is invalid */
-#define ERROR_BADNAME  0x0400  /* newsgroup name is invalid */
-#define ERROR_FORMAT   0x0800  /* entry line is malformed */
-
-#define IS_IGNORE(ign) ((ign) & (CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
-#define IS_ERROR(ign) ((ign) & ~(CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
-
-#define NOHOST 0               /* neither host1 nor host2 */
-#define HOSTID1 1              /* entry from the first host */
-#define HOSTID2 2              /* entry from the second host */
-
-#define CHUNK 5000             /* number of elements to alloc at a time */
-
-#define TYPES "ymjnx="         /* group types (1st char of 4th active fld) */
-#define TYPECNT (sizeof(TYPES)-1)
-
-#define DEF_HI   "0000000000"  /* default hi string value for new groups */
-#define DEF_LOW  "0000000001"  /* default low string value for new groups */
-#define WATER_LEN 10           /* string length of hi/low water mark */
-
-#define DEF_NAME "actsync"     /* default name to use for ctlinnd newgroup */
-
-#define MIN_UNCHG (double)96.0 /* min % of host1 lines unchanged allowed */
-
-#define DEV_NULL "/dev/null"   /* path to the bit bucket */
-#define CTLINND_NAME "ctlinnd" /* basename of ctlinnd command */
-#define CTLINND_TIME_OUT "-t30"        /* seconds to wait before timeout */
-
-#define READ_SIDE 0            /* read side of a pipe */
-#define WRITE_SIDE 1           /* write side of a pipe */
-
-#define EQ_LOOP 16             /* give up if =eq loop/chain is this long */
-#define NOT_REACHED 127                /* exit value if unable to get active files */
-
-#define NEWGRP_EMPTY 0         /* no new group dir was found */
-#define NEWGRP_NOCHG 1         /* new group dir found but no hi/low change */
-#define NEWGRP_CHG 2           /* new group dir found but no hi/low change */
-
-/* -b macros */
-#define BORK_CHECK(hostid)  \
-    ((hostid == HOSTID1 && bork_host1_flag) || \
-     (hostid == HOSTID2 && bork_host2_flag))
-
-/* -d macros */
-#define NUM_CHECK(hostid)  \
-    ((hostid == HOSTID1 && num_host1_flag) || \
-     (hostid == HOSTID2 && num_host2_flag))
-
-/* -t macros */
-#define TOP_CHECK(hostid)  \
-    ((hostid == HOSTID1 && t_host1_flag) || \
-     (hostid == HOSTID2 && t_host2_flag))
-
-/* -o output types */
-#define OUTPUT_ACTIVE 1                /* output in active file format */
-#define OUTPUT_CTLINND 2       /* output in ctlinnd change commands */
-#define OUTPUT_EXEC 3          /* no output, safely exec commands */
-#define OUTPUT_IEXEC 4         /* no output, exec commands interactively */
-
-/* -q macros */
-#define QUIET(hostid)  \
-    ((hostid == HOSTID1 && quiet_host1) || (hostid == HOSTID2 && quiet_host2))
-
-/* -v verbosity level */
-#define VER_MIN 0              /* minimum -v level */
-#define VER_NONE 0             /* no -v output */
-#define VER_SUMM_IF_WORK 1     /* output summary if actions were performed */
-#define VER_REPT_IF_WORK 2     /* output summary & actions only if performed */
-#define VER_REPORT 3           /* output summary & actions performed */
-#define VER_FULL 4             /* output all summary, actins and debug */
-#define VER_MAX 4              /* maximum -v level */
-#define D_IF_SUMM (v_flag >= VER_SUMM_IF_WORK) /* true => give summary always */
-#define D_REPORT (v_flag >= VER_REPT_IF_WORK)  /* true => give reports */
-#define D_BUG (v_flag == VER_FULL)            /* true => debug processing */
-#define D_SUMMARY (v_flag >= VER_REPORT)       /* true => give summary always */
-
-/* flag and arg related defaults */
-int bork_host1_flag = 0;       /* 1 => -b 1 or -b 12 or -b 21 given */
-int bork_host2_flag = 0;       /* 1 => -b 2 or -b 12 or -b 21 given */
-int num_host1_flag = 0;        /* 1 => -d 1 or -d 12 or -d 21 given */
-int num_host2_flag = 0;        /* 1 => -d 2 or -d 12 or -d 21 given */
-char *ign_file = NULL;         /* default ignore file */
-int ign_host1_flag = 1;                /* 1 => -i ign_file applies to host1 */
-int ign_host2_flag = 1;                /* 1 => -i ign_file applies to host2 */
-int g_flag = 0;                        /* ignore grps deeper than > g_flag, 0=>dont */
-int k_flag = 0;                        /* 1 => -k given */
-int l_host1_flag = HOSTID1;    /* HOSTID1 => host1 =group error detection */
-int l_host2_flag = HOSTID2;    /* HOSTID2 => host2 =group error detection */
-int m_flag = 0;                        /* 1 => merge active files, don't sync */
-const char *new_name = DEF_NAME;       /* ctlinnd newgroup name */
-int o_flag = OUTPUT_CTLINND;   /* default output type */
-double p_flag = MIN_UNCHG;     /* min % host1 lines allowed to be unchanged */
-int host1_errs = 0;            /* errors found in host1 active file */
-int host2_errs = 0;            /* errors found in host2 active file */
-int quiet_host1 = 0;           /* 1 => -q 1 or -q 12 or -q 21 given */
-int quiet_host2 = 0;           /* 1 => -q 2 or -q 12 or -q 21 given */
-int s_flag = 0;                        /* max group size (length), 0 => do not check */
-int t_host1_flag = 0;          /* 1 => -t 1 or -t 12 or -t 21 given */
-int t_host2_flag = 1;          /* 1 => -t 2 or -d 12 or -t 21 given */
-int no_new_hier = 0;           /* 1 => -T; no new hierarchies */
-int host2_hilow_newgrp = 0;    /* 1 => use host2 hi/low on new groups */
-int host2_hilow_all = 0;       /* 1 => use host2 hi/low on all groups */
-int host1_ign_print = 0;       /* 1 => print host1 ignored groups too */
-int v_flag = 0;                        /* default verbosity level */
-int z_flag = 4;                        /* sleep z_flag sec per exec if -o x */
-int A_flag = 0;
-
-/* forward declarations */
-static struct grp *get_active();    /* get an active file from a remote host */
-static int bad_grpname();          /* test if string is a valid group name */
-static struct pat *get_ignore();    /* read in an ignore file */
-static void ignore();              /* ignore newsgroups given an ignore list */
-static int merge_cmp();                    /* qsort compare for active file merge */
-static void merge_grps();          /* merge groups from active files */
-static int active_cmp();           /* qsort compare for active file output */
-static void output_grps();         /* output the merged groups */
-static void process_args();        /* process command line arguments */
-static void error_mark();          /* mark for removal, error grps from host */
-static int eq_merge_cmp();         /* qsort compare for =type grp processing */
-static int mark_eq_probs();        /* mark =type problems from a host */
-static int exec_cmd();             /* exec a ctlinnd command */
-static int new_top_hier();         /* see if we have a new top level */
-
-int
-main(argc, argv)
-    int         argc;                  /* arg count */
-    char *argv[];              /* the args */
-{
-    struct grp *grp;           /* struct grp array for host1 & host2 */
-    struct pat *ignor;         /* ignore list from ignore file */
-    int grplen;                        /* length of host1/host2 group array */
-    int iglen;                 /* length of ignore list */
-    char *host1;               /* host to change */
-    char *host2;               /* comparison host */
-
-    /* First thing, set up our identity. */
-    message_program_name = "actsync";
-
-    /* Read in default info from inn.conf. */
-    if (!innconf_read(NULL))
-        exit(1);
-    process_args(argc, argv, &host1, &host2);
-
-    /* obtain the active files */
-    grp = get_active(host1, HOSTID1, &grplen, NULL, &host1_errs);
-    grp = get_active(host2, HOSTID2, &grplen, grp, &host2_errs);
-
-    /* ignore groups from both active files, if -i */
-    if (ign_file != NULL) {
-
-       /* read in the ignore file */
-       ignor = get_ignore(ign_file, &iglen);
-
-       /* ignore groups */
-       ignore(grp, grplen, ignor, iglen);
-    }
-
-    /* compare groups from both hosts */
-    merge_grps(grp, grplen, host1, host2);
-
-    /* mark for removal, error groups from host1 if -e */
-    if (! k_flag) {
-
-       /* mark error groups for removal */
-       error_mark(grp, grplen, HOSTID1);
-    }
-
-    /* output result of merge */
-    output_grps(grp, grplen);
-
-    /* all done */
-    exit(0);
-}
-
-/*
- * process_args - process the command line arguments
- *
- * given:
- *     argc    arg count
- *     argv    the args
- *     host1   name of first host (may be 2nd if -R)
- *     host2   name of second host2 *may be 1st if -R)
- */
-static void
-process_args(argc, argv, host1, host2)
-    int argc;          /* arg count */
-    char *argv[];      /* the arg array */
-    char **host1;      /* where to place name of host1 */
-    char **host2;      /* where to place name of host2 */
-{
-    char *def_serv = NULL;     /* name of default server */
-    int i;
-
-    /* parse args */
-    while ((i = getopt(argc,argv,"Ab:d:g:i:I:kl:mn:o:p:q:s:t:Tv:z:")) != EOF) {
-       switch (i) {
-       case 'A':
-           A_flag = 1;
-           break;
-       case 'b':               /* -b {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               bork_host1_flag = 0;
-               bork_host2_flag = 0;
-               break;
-           case 1:
-               bork_host1_flag = 1;
-               break;
-           case 2:
-               bork_host2_flag = 1;
-               break;
-           case 12:
-           case 21:
-               bork_host1_flag = 1;
-               bork_host2_flag = 1;
-               break;
-           default:
-                warn("-b option must be 0, 1, 2, 12, or 21");
-                die("%s", usage);
-           }
-           break;
-       case 'd':               /* -d {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               num_host1_flag = 0;
-               num_host2_flag = 0;
-               break;
-           case 1:
-               num_host1_flag = 1;
-               break;
-           case 2:
-               num_host2_flag = 1;
-               break;
-           case 12:
-           case 21:
-               num_host1_flag = 1;
-               num_host2_flag = 1;
-               break;
-           default:
-                warn("-d option must be 0, 1, 2, 12, or 21");
-               die("%s", usage);
-           }
-           break;
-       case 'g':               /* -g max */
-           g_flag = atoi(optarg);
-           break;
-       case 'i':               /* -i ignore_file */
-           ign_file = optarg;
-           break;
-       case 'I':               /* -I {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               ign_host1_flag = 0;
-               ign_host2_flag = 0;
-               break;
-           case 1:
-               ign_host1_flag = 1;
-               ign_host2_flag = 0;
-               break;
-           case 2:
-               ign_host1_flag = 0;
-               ign_host2_flag = 1;
-               break;
-           case 12:
-           case 21:
-               ign_host1_flag = 1;
-               ign_host2_flag = 1;
-               break;
-           default:
-                warn("-I option must be 0, 1, 2, 12, or 21");
-               die("%s", usage);
-           }
-           break;
-       case 'k':               /* -k */
-           k_flag = 1;
-           break;
-       case 'l':               /* -l {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               l_host1_flag = NOHOST;
-               l_host2_flag = NOHOST;
-               break;
-           case 1:
-               l_host1_flag = HOSTID1;
-               l_host2_flag = NOHOST;
-               break;
-           case 2:
-               l_host1_flag = NOHOST;
-               l_host2_flag = HOSTID2;
-               break;
-           case 12:
-           case 21:
-               l_host1_flag = HOSTID1;
-               l_host2_flag = HOSTID2;
-               break;
-           default:
-                warn("-l option must be 0, 1, 2, 12, or 21");
-               die("%s", usage);
-           }
-           break;
-       case 'm':               /* -m */
-           m_flag = 1;
-           break;
-       case 'n':               /* -n name */
-           new_name = optarg;
-           break;
-       case 'o':               /* -o out_type */
-           switch (optarg[0]) {
-           case 'a':
-               o_flag = OUTPUT_ACTIVE;
-               switch (optarg[1]) {
-               case '1':
-                   switch(optarg[2]) {
-                   case 'K':   /* -o a1K */
-                       host1_ign_print = 1;
-                       host2_hilow_all = 1;
-                       host2_hilow_newgrp = 1;
-                       break;
-                   case 'k':   /* -o a1k */
-                       host1_ign_print = 1;
-                       host2_hilow_newgrp = 1;
-                       break;
-                   default:    /* -o a1 */
-                       host1_ign_print = 1;
-                       break;
-                   }
-                   break;
-               case 'K':
-                   switch(optarg[2]) {
-                   case '1':   /* -o aK1 */
-                       host1_ign_print = 1;
-                       host2_hilow_all = 1;
-                       host2_hilow_newgrp = 1;
-                       break;
-                   default:    /* -o aK */
-                       host2_hilow_all = 1;
-                       host2_hilow_newgrp = 1;
-                       break;
-                   };
-                   break;
-               case 'k':
-                   switch(optarg[2]) {
-                   case '1':   /* -o ak1 */
-                       host1_ign_print = 1;
-                       host2_hilow_newgrp = 1;
-                       break;
-                   default:    /* -o ak */
-                       host2_hilow_newgrp = 1;
-                       break;
-                   };
-                   break;
-               case '\0':      /* -o a */
-                   break;
-               default:
-                    warn("-o type must be a, a1, ak, aK, ak1, or aK1");
-                   die("%s", usage);
-               }
-               break;
-           case 'c':
-               o_flag = OUTPUT_CTLINND;
-               break;
-           case 'x':
-               if (optarg[1] == 'i') {
-                   o_flag = OUTPUT_IEXEC;
-               } else {
-                   o_flag = OUTPUT_EXEC;
-               }
-               break;
-           default:
-                warn("-o type must be a, a1, ak, aK, ak1, aK1, c, x, or xi");
-               die("%s", usage);
-           }
-           break;
-       case 'p':               /* -p %_min_host1_change */
-           /* parse % into [0,100] */
-           p_flag = atof(optarg);
-           if (p_flag > (double)100.0) {
-               p_flag = (double)100.0;
-           } else if (p_flag < (double)0.0) {
-               p_flag = (double)0.0;
-           }
-           break;
-       case 'q':               /* -q {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               quiet_host1 = 0;
-               quiet_host2 = 0;
-               break;
-           case 1:
-               quiet_host1 = 1;
-               break;
-           case 2:
-               quiet_host2 = 1;
-               break;
-           case 12:
-           case 21:
-               quiet_host1 = 1;
-               quiet_host2 = 1;
-               break;
-           default:
-                warn("-q option must be 0, 1, 2, 12, or 21");
-               die("%s", usage);
-           }
-           break;
-       case 's':               /* -s size */
-           s_flag = atoi(optarg);
-           break;
-       case 't':               /* -t {0|1|2|12|21} */
-           switch (atoi(optarg)) {
-           case 0:
-               t_host1_flag = NOHOST;
-               t_host2_flag = NOHOST;
-               break;
-           case 1:
-               t_host1_flag = HOSTID1;
-               t_host2_flag = NOHOST;
-               break;
-           case 2:
-               t_host1_flag = NOHOST;
-               t_host2_flag = HOSTID2;
-               break;
-           case 12:
-           case 21:
-               t_host1_flag = HOSTID1;
-               t_host2_flag = HOSTID2;
-               break;
-           default:
-                warn("-t option must be 0, 1, 2, 12, or 21");
-               die("%s", usage);
-           }
-           break;
-       case 'T':               /* -T */
-           no_new_hier = 1;
-           break;
-       case 'v':               /* -v verbose_lvl */
-           v_flag = atoi(optarg);
-           if (v_flag < VER_MIN || v_flag > VER_MAX) {
-                warn("-v level must be >= %d and <= %d", VER_MIN, VER_MAX);
-               die("%s", usage);
-           }
-           break;
-       case 'z':               /* -z sec */
-           z_flag = atoi(optarg);
-           break;
-       default:
-            warn("unknown flag");
-           die("%s", usage);
-       }
-    }
-
-    /* process the remaining args */
-    argc -= optind;
-    argv += optind;
-    *host1 = NULL;
-    switch (argc) {
-    case 1:
-       /* assume host1 is the local server */
-       *host2 = argv[0];
-       break;
-    case 2:
-       *host1 = argv[0];
-       *host2 = argv[1];
-       break;
-    default:
-        warn("expected 1 or 2 host args, found %d", argc);
-       die("%s", usage);
-    }
-
-    /* determine default host name if needed */
-    if (*host1 == NULL || strcmp(*host1, "-") == 0) {
-       def_serv = innconf->server;
-       *host1 = def_serv;
-    }
-    if (*host2 == NULL || strcmp(*host2, "-") == 0) {
-       def_serv = innconf->server;
-       *host2 = def_serv;
-    }
-    if (*host1 == NULL || *host2 == NULL)
-        die("unable to determine default server name");
-    if (D_BUG && def_serv != NULL)
-        warn("STATUS: using default server: %s", def_serv);
-
-    /* processing done */
-    return;
-}
-
-/*
- * get_active - get an active file from a host
- *
- * given:
- *     host    host to contact or file to read, NULL => local server
- *     hostid  HOST_ID of host
- *     len     pointer to length of grp return array
- *     grp     existing host array to add, or NULL
- *     errs    count of lines that were found to have some error
- *
- * returns;
- *     Pointer to an array of grp structures describing each active entry.
- *     Does not return on fatal error.
- *
- * If host starts with a '/' or '.', then it is assumed to be a local file.
- * In that case, the local file is opened and read.
- */
-static struct grp *
-get_active(host, hostid, len, grp, errs)
-    char *host;                        /* the host to contact */
-    int hostid;                        /* HOST_ID of host */
-    int *len;                  /* length of returned grp array in elements */
-    struct grp* grp;           /* existing group array or NULL */
-    int *errs;                 /* line error count */
-{
-    FILE *active;              /* stream for fetched active data */
-    FILE *FromServer;          /* stream from server */
-    FILE *ToServer;            /* stream to server */
-    QIOSTATE *qp;              /* QIO active state */
-    char buff[8192+1];         /* QIO buffer */
-    char *line;                        /* the line just read */
-    struct grp *ret;           /* array of groups to return */
-    struct grp *cur;           /* current grp entry being formed */
-    int max;                   /* max length of ret */
-    int cnt;                   /* number of entries read */
-    int ucnt;                  /* number of entries to be used */
-    int namelen;               /* length of newsgroup name */
-    int is_file;               /* 1 => host is actually a filename */
-    int num_check;             /* true => check for all numeric components */
-    char *rhost;
-    int rport;
-    char *p;
-    int i;
-
-    /* firewall */
-    if (len == NULL)
-        die("internal error #1: len is NULL");
-    if (errs == NULL)
-        die("internal error #2: errs in NULL");
-    if (D_BUG)
-        warn("STATUS: obtaining active file from %s", host);
-
-    /* setup return array if needed */
-    if (grp == NULL) {
-        ret = xmalloc(CHUNK * sizeof(struct grp));
-       max = CHUNK;
-       *len = 0;
-
-    /* or prep to use the existing array */
-    } else {
-       ret = grp;
-       max = ((*len + CHUNK-1)/CHUNK)*CHUNK;
-    }
-
-    /* check for host being a filename */
-    if (host != NULL && (host[0] == '/' || host[0] == '.')) {
-
-       /* note that host is actually a file */
-       is_file = 1;
-
-       /* setup to read the local file quickly */
-       if ((qp = QIOopen(host)) == NULL)
-            sysdie("cannot open active file");
-
-    /* case: host is a hostname or NULL (default server) */
-    } else {
-
-       /* note that host is actually a hostname or NULL */
-       is_file = 0;
-
-        /* prepare remote host variables */
-       if ((p = strchr(host, ':')) != NULL) {
-               rport = atoi(p + 1);
-               *p = '\0';
-               rhost = xstrdup(host);
-               *p = ':';
-       } else {
-               rhost = xstrdup(host);
-               rport = NNTP_PORT;
-       }
-
-       /* open a connection to the server */
-       buff[0] = '\0';
-       if (NNTPconnect(rhost, rport, &FromServer, &ToServer, buff) < 0)
-            die("cannot connect to server: %s",
-                buff[0] ? buff : strerror(errno));
-
-        if (A_flag && NNTPsendpassword(rhost, FromServer, ToServer) < 0)
-            die("cannot authenticate to server");
-
-       free(rhost);
-
-       /* get the active data from the server */
-       active = CAlistopen(FromServer, ToServer, NULL);
-       if (active == NULL)
-            sysdie("cannot retrieve data");
-
-       /* setup to read the retrieved data quickly */
-       if ((qp = QIOfdopen((int)fileno(active))) == NULL)
-            sysdie("cannot read temp file");
-    }
-
-    /* scan server's output, processing appropriate lines */
-    num_check = NUM_CHECK(hostid);
-    for (cnt=0, ucnt=0; (line = QIOread(qp)) != NULL; ++(*len), ++cnt) {
-
-       /* expand return array if needed */
-       if (*len >= max) {
-           max += CHUNK;
-            ret = xrealloc(ret, sizeof(struct grp) * max);
-       }
-
-       /* setup the next return element */
-       cur = &ret[*len];
-       cur->ignore = NOT_IGNORED;
-       cur->hostid = hostid;
-       cur->linenum = cnt+1;
-       cur->output = 0;
-       cur->remove = 0;
-       cur->name = NULL;
-       cur->hi = NULL;
-       cur->low = NULL;
-       cur->type = NULL;
-       cur->outhi = NULL;
-       cur->outlow = NULL;
-       cur->outtype = NULL;
-
-       /* obtain a copy of the current line */
-        cur->name = xstrdup(line);
-
-       /* get the group name */
-       if ((p = strchr(cur->name, ' ')) == NULL) {
-           if (!QUIET(hostid))
-                warn("line %d from %s is malformed, skipping line", cnt + 1,
-                     host);
-
-           /* don't form an entry for this group */
-           --(*len);
-           continue;
-       }
-       *p = '\0';
-       namelen = p - cur->name;
-
-       /* find the other 3 fields, ignore if not found */
-       cur->hi = p+1;
-       if ((p = strchr(p + 1, ' ')) == NULL) {
-           if (!QUIET(hostid))
-                warn("skipping malformed line %d (field 2) from %s", cnt + 1,
-                     host);
-
-           /* don't form an entry for this group */
-           --(*len);
-           continue;
-       }
-       *p = '\0';
-       cur->low = p+1;
-       if ((p = strchr(p + 1, ' ')) == NULL) {
-           if (!QUIET(hostid))
-                warn("skipping malformed line %d (field 3) from %s", cnt + 1,
-                     host);
-
-           /* don't form an entry for this group */
-           --(*len);
-           continue;
-       }
-       *p = '\0';
-       cur->type = p+1;
-       if ((p = strchr(p + 1, ' ')) != NULL) {
-           if (!QUIET(hostid))
-                warn("skipping line %d from %s, it has more than 4 fields",
-                     cnt + 1, host);
-
-           /* don't form an entry for this group */
-           --(*len);
-           continue;
-       }
-
-       /* check for bad group name */
-       if (bad_grpname(cur->name, num_check)) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s has a bad newsgroup name",
-                     cnt + 1, cur->name, host);
-           cur->ignore |= ERROR_BADNAME;
-           continue;
-       }
-
-       /* check for long name if requested */
-       if (s_flag > 0 && strlen(cur->name) > (size_t)s_flag) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s has a name that is too long",
-                     cnt + 1, cur->name, host);
-           cur->ignore |= ERROR_BADNAME;
-           continue;
-       }
-
-       /* look for only a bad top level element if the proper -t was given */
-       if (TOP_CHECK(hostid)) {
-
-           /* look for a '.' in the name */
-           if (strcmp(cur->name, "junk") != 0 && 
-               strcmp(cur->name, "control") != 0 && 
-               strcmp(cur->name, "to") != 0 && 
-               strcmp(cur->name, "test") != 0 && 
-               strcmp(cur->name, "general") != 0 && 
-               strchr(cur->name, '.') == NULL) {
-               if (!QUIET(hostid))
-                    warn("line %d <%s> from %s is an invalid top level name",
-                         cnt + 1, cur->name, host);
-               cur->ignore |= ERROR_BADNAME;
-               continue;
-           }
-       }
-
-       /* look for *.bork.bork.bork groups if the proper -b was given */
-       if (BORK_CHECK(cur->hostid)) {
-           int elmlen;         /* length of element */
-           char *q;            /* beyond end of element */
-
-           /* scan the name backwards */
-           q = &(cur->name[namelen]);
-           for (p = &(cur->name[namelen-1]); p >= cur->name; --p) {
-               /* if '.', see if this is a bork element */
-               if (*p == '.') {
-                   /* see if the bork element is short enough */
-                   elmlen = q-p;
-                   if (3*elmlen <= q-cur->name) {
-                       /* look for a triple match */
-                       if (strncmp(p,p-elmlen,elmlen) == 0 &&
-                           strncmp(p,p-(elmlen*2),elmlen) == 0) {
-                           /* found a *.bork.bork.bork group */
-                           cur->ignore |= CHECK_BORK;
-                           break;
-                       }
-                   }
-                   /* note the end of a new element */
-                   q = p;
-               }
-           }
-       }
-
-       /* 
-        * check for bad chars in the hi water mark 
-        */
-       for (p=cur->hi, i=0; *p && isascii(*p) && isdigit((int)*p); ++p, ++i) {
-       }
-       if (*p) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s has non-digits in hi water",
-                     cnt + 1, cur->name, cur->hi);
-           cur->ignore |= ERROR_FORMAT;
-           continue;
-       }
-
-       /*
-        * check for excessive hi water length
-        */
-       if (i > WATER_LEN) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s hi water len: %d < %d",
-                     cnt + 1, cur->name, cur->hi, i, WATER_LEN);
-           cur->ignore |= ERROR_FORMAT;
-           continue;
-       }
-
-       /*
-        * if the hi water length is too small, malloc and resize
-        */
-       if (i != WATER_LEN) {
-            p = xmalloc(WATER_LEN + 1);
-           memcpy(p, cur->hi, ((i > WATER_LEN) ? WATER_LEN : i)+1);
-       }
-
-       /* 
-        * check for bad chars in the low water mark 
-        */
-       for (p=cur->low, i=0; *p && isascii(*p) && isdigit((int)*p); ++p, ++i) {
-       }
-       if (*p) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s has non-digits in low water",
-                    cnt + 1, cur->name, cur->low);
-           cur->ignore |= ERROR_FORMAT;
-           continue;
-       }
-
-       /*
-        * check for excessive low water length
-        */
-       if (i > WATER_LEN) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s low water len: %d < %d",
-                    cnt + 1, cur->name, cur->hi, i, WATER_LEN);
-           cur->ignore |= ERROR_FORMAT;
-           continue;
-       }
-
-       /*
-        * if the low water length is too small, malloc and resize
-        */
-       if (i != WATER_LEN) {
-            p = xmalloc(WATER_LEN + 1);
-           memcpy(p, cur->low, ((i > WATER_LEN) ? WATER_LEN : i)+1);
-       }
-
-       /* check for a bad group type */
-       switch (cur->type[0]) {
-       case 'y':
-               /* of COURSE: collabra has incompatible flags. but it   */
-               /* looks like they can be fixed easily enough.          */
-               if (cur->type[1] == 'g') {
-                       cur->type[1] = '\0';
-               }
-       case 'm':
-       case 'j':
-       case 'n':
-       case 'x':
-           if (cur->type[1] != '\0') {
-               if (!QUIET(hostid))
-                    warn("line %d <%s> from %s has a bad newsgroup type",
-                         cnt + 1, cur->name, host);
-               cur->ignore |= ERROR_BADTYPE;
-           }
-           break;
-       case '=':
-           if (cur->type[1] == '\0') {
-               if (!QUIET(hostid))
-                    warn("line %d <%s> from %s has an empty =group name",
-                         cnt + 1, cur->name, host);
-               cur->ignore |= ERROR_BADTYPE;
-           }
-           break;
-       default:
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s has an unknown newsgroup type",
-                     cnt + 1, cur->name, host);
-           cur->ignore |= ERROR_BADTYPE;
-           break;
-       }
-       if (cur->ignore & ERROR_BADTYPE) {
-           continue;
-       }
-
-       /* if an = type, check for bad = name */
-       if (cur->type[0] == '=' && bad_grpname(&(cur->type[1]), num_check)) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s is equivalenced to a bad name:"
-                     " <%s>", cnt+1, cur->name, host,
-                    (cur->type) ? cur->type : "NULL");
-           cur->ignore |= ERROR_EQNAME;
-           continue;
-       }
-
-       /* if an = type, check for long = name if requested */
-       if (cur->type[0] == '=' && s_flag > 0 &&
-           strlen(&(cur->type[1])) > (size_t)s_flag) {
-           if (!QUIET(hostid))
-                warn("line %d <%s> from %s is equivalenced to a long name:"
-                     " <%s>", cnt+1, cur->name, host,
-                    (cur->type) ? cur->type : "NULL");
-           cur->ignore |= ERROR_EQNAME;
-           continue;
-       }
-
-       /* count this entry which will be used */
-       ++ucnt;
-    }
-    if (D_BUG)
-        warn("STATUS: read %d groups, will merge %d groups from %s",
-             cnt, ucnt, host);
-
-    /* count the errors */
-    *errs = cnt - ucnt;
-    if (D_BUG)
-        warn("STATUS: found %d line errors from %s", *errs, host);
-
-    /* determine why we stopped */
-    if (QIOerror(qp))
-        sysdie("cannot read temp file for %s at line %d", host, cnt);
-    else if (QIOtoolong(qp))
-        sysdie("line %d from host %s is too long", cnt, host);
-
-    /* all done */
-    if (is_file) {
-       QIOclose(qp);
-    } else {
-       CAclose();
-       fprintf(ToServer, "quit\r\n");
-       fclose(ToServer);
-       fgets(buff, sizeof buff, FromServer);
-       fclose(FromServer);
-    }
-    return ret;
-}
-
-/*
- * bad_grpname - test if the string is a valid group name
- *
- * Newsgroup names must consist of only alphanumeric chars and
- * characters from the following regular expression:
- *
- *     [.+-_]
- *
- * One cannot have two '.'s in a row.  The first character must be
- * alphanumeric.  The character following a '.' must be alphanumeric.
- * The name cannot end in a '.' character.
- *
- * If we are checking for all numeric compnents, (see num_chk) then
- * a component cannot be all numeric.  I.e,. there must be a non-numeric
- * character in the name, there must be a non-numeric character between
- * the start and the first '.', there must be a non-numeric character
- * between two '.'s anmd there must be a non-numeric character between
- * the last '.' and the end.
- *
- * given:
- *     name    newsgroup name to check
- *     num_chk true => all numeric newsgroups components are invalid
- *             false => do not check for numeric newsgroups
- *
- * returns:
- *     0       group is ok
- *     1       group is bad
- */
-static int
-bad_grpname(name, num_chk)
-    char *name;                        /* newsgroup name to check */
-    int num_chk;               /* true => check for numeric newsgroup */
-{
-    char *p;
-    int non_num;       /* true => found a non-numeric, non-. character */
-    int level;         /* group levels (.'s) */
-
-    /* firewall */
-    if (name == NULL) {
-       return 1;
-    }
-
-    /* must start with a alpha numeric ascii character */
-    if (!isascii(name[0])) {
-       return 1;
-    }
-    /* set non_num as needed */
-    if (isalpha((int)name[0])) {
-       non_num = true;
-    } else if ((int)isdigit((int)name[0])) {
-       non_num = false;
-    } else {
-       return 1;
-    }
-
-    /* scan each char */
-    level = 0;
-    for (p=name+1; *p; ++p) {
-
-       /* name must contain ASCII chars */
-       if (!isascii(*p)) {
-           return 1;
-       }
-
-       /* alpha chars are ok */
-       if (isalpha((int)*p)) {
-           non_num = true;
-           continue;
-       }
-
-       /* numeric chars are ok */
-       if (isdigit((int)*p)) {
-           continue;
-       }
-
-       /* +, - and _ are ok */
-       if (*p == '+' || *p == '-' || *p == '_') {
-           non_num = true;
-           continue;
-       }
-
-       /* check for the '.' case */
-       if (*p == '.') {
-           /*
-            * look for groups that are too deep, if requested by -g
-            */
-           if (g_flag > 0 && ++level > g_flag) {
-               /* we are too deep */
-               return 1;
-           }
-
-           /*
-            * A '.' is ok as long as the next character is alphanumeric.
-            * This imples that '.' cannot before a previous '.' and
-            * that it cannot be at the end.
-            *
-            * If we are checking for all numeric compnents, then
-            * '.' is ok if we saw a non-numeric char before the
-            * last '.', or before the beginning if no previous '.'
-            * has been seen.
-            */
-           if ((!num_chk || non_num) && isascii(*(p+1)) && isalnum((int)*(p+1))) {
-               ++p;            /* '.' is ok, and so is the next char */
-               if (isdigit((int)*p)) { /* reset non_num as needed */
-                   non_num = false;
-               } else {
-                   non_num = true;
-               }
-               continue;
-           }
-       }
-
-       /* this character must be invalid */
-       return 1;
-    }
-    if (num_chk && !non_num) {
-       /* last component is all numeric */
-       return 1;
-    }
-
-    /* the name must be ok */
-    return 0;
-}
-
-/*
- * get_ignore - get the ignore list from an ignore file
- *
- * given:
- *     filename        name of the ignore file to read
- *     *len            pointer to length of ignore return array
- *
- * returns:
- *     returns a malloced ignore pattern array, changes len
- *
- * An ignore file is of the form:
- *
- *     # this is a comment which is ignored
- *     # comments begin at the first # character
- *     # comments may follow text on the same line
- *
- *     # blank lines are ignored too
- *
- *     # lines are [ic] <spaces-tabs> pattern [<spaces-tabs> type] ...
- *     i    foo.*              # ignore foo.* groups,
- *     c    foo.bar m          # but check foo.bar if moderated
- *     c    foo.keep.*         # and check foo.keep.*
- *     i    foo.keep.* j =alt.*      # except when foo.keep.* is junked
- *                                   #     or equivalenced to an alt.* group
- *
- * The 'i' value means ignore, 'c' value means 'compare'.   The last pattern
- * that matches a group determines the fate of the group.  By default all
- * groups are included.
- *
- * NOTE: Only one '=name' is allowed per line.
- *       "=" is considered to be equivalent to "=*".
- */
-static struct pat *
-get_ignore(filename, len)
-    char *filename;            /* name of the ignore file to read */
-    int *len;                  /* length of return array */
-{
-    QIOSTATE *qp;              /* QIO ignore file state */
-    char *line;                        /* the line just read */
-    struct pat *ret;           /* array of ignore patterns to return */
-    struct pat *cur;           /* current pattern entry being formed */
-    int max;                   /* max length (in elements) of ret */
-    int linenum;               /* current line number */
-    char *p;
-    int i;
-
-    /* firewall */
-    if (filename == NULL)
-        die("internal error #3: filename is NULL");
-    if (len == NULL)
-        die("internal error #4: len is NULL");
-    if (D_BUG)
-        warn("STATUS: reading ignore file %s", filename);
-
-    /* setup return array */
-    ret = xmalloc(CHUNK * sizeof(struct grp));
-    max = CHUNK;
-
-    /* setup to read the ignore file data quickly */
-    if ((qp = QIOopen(filename)) == NULL)
-        sysdie("cannot read ignore file %s", filename);
-
-    /* scan server's output, displaying appropriate lines */
-    *len = 0;
-    for (linenum = 1; (line = QIOread(qp)) != NULL; ++linenum) {
-
-       /* expand return array if needed */
-       if (*len >= max) {
-           max += CHUNK;
-            ret = xrealloc(ret, sizeof(struct pat) * max);
-       }
-
-       /* remove any trailing comments */
-       p = strchr(line, '#');
-       if (p != NULL) {
-           *p = '\0';
-       }
-
-       /* remove any trailing spaces and tabs */
-       for (p = &line[strlen(line)-1];
-            p >= line && (*p == ' ' || *p == '\t');
-            --p) {
-           *p = '\0';
-       }
-
-       /* ignore line if the remainder of the line is empty */
-       if (line[0] == '\0') {
-           continue;
-       }
-
-       /* ensure that the line starts with an i or c token */
-       if ((line[0] != 'i' && line[0] != 'c') ||
-           (line[1] != ' ' && line[1] != '\t'))
-            die("first token is not i or c in line %d of %s", linenum,
-                filename);
-
-       /* ensure that the second newsgroup pattern token follows */
-       p = strtok(line+2, " \t");
-       if (p == NULL)
-            die("did not find 2nd field in line %d of %s", linenum,
-                filename);
-
-       /* setup the next return element */
-       cur = &ret[*len];
-       cur->pat = NULL;
-       cur->type_match = 0;
-       cur->y_type = 0;
-       cur->m_type = 0;
-       cur->n_type = 0;
-       cur->j_type = 0;
-       cur->x_type = 0;
-       cur->eq_type = 0;
-       cur->epat = NULL;
-       cur->ignore = (line[0] == 'i');
-
-       /* obtain a copy of the newsgroup pattern token */
-        cur->pat = xstrdup(p);
-
-       /* process any other type tokens */
-       for (p=strtok(NULL, " \t"), i=3;
-            p != NULL;
-            p=strtok(NULL, " \t"), ++i) {
-
-           /* ensure that this next token is a valid type */
-           switch (p[0]) {
-           case 'y':
-           case 'm':
-           case 'j':
-           case 'n':
-           case 'x':
-               if (p[1] != '\0') {
-                    warn("field %d on line %d of %s not a valid type",
-                         i, linenum, filename);
-                    die("valid types are a char from [ymnjx=] or =name");
-               }
-               break;
-           case '=':
-               break;
-           default:
-                warn("field %d on line %d of %s is not a valid type",
-                     i, linenum, filename);
-                die("valid types are a char from [ymnjx=] or =name");
-            }
-
-           /* note that we have a type specific pattern */
-           cur->type_match = 1;
-
-           /* ensure that type is not a duplicate */
-           if ((p[0] == 'y' && cur->y_type) ||
-               (p[0] == 'm' && cur->m_type) ||
-               (p[0] == 'n' && cur->n_type) ||
-               (p[0] == 'j' && cur->j_type) ||
-               (p[0] == 'x' && cur->x_type) ||
-               (p[0] == '=' && cur->eq_type)) {
-                warn("only one %c type allowed per line", p[0]);
-                die("field %d on line %d of %s is a duplicate type",
-                    i, linenum, filename);
-           }
-
-           /* note what we have seen */
-           switch (p[0]) {
-           case 'y':
-               cur->y_type = 1;
-               break;
-           case 'm':
-               cur->m_type = 1;
-               break;
-           case 'j':
-               cur->j_type = 1;
-               break;
-           case 'n':
-               cur->n_type = 1;
-               break;
-           case 'x':
-               cur->x_type = 1;
-               break;
-           case '=':
-               cur->eq_type = 1;
-               if (p[0] == '=' && p[1] != '\0')
-                    cur->epat = xstrdup(p + 1);
-               break;
-           }
-
-           /* object if too many fields */
-           if (i-3 > TYPECNT)
-                die("too many fields on line %d of %s", linenum, filename);
-       }
-
-       /* count another pat element */
-       ++(*len);
-    }
-
-    /* return the pattern array */
-    return ret;
-}
-
-/*
- * ignore - ignore newsgroups given an ignore list
- *
- * given:
- *     grp     array of groups
- *     grplen  length of grp array in elements
- *     igcl    array of ignore
- *     iglen   length of igcl array in elements
- */
-static void
-ignore(grp, grplen, igcl, iglen)
-    struct grp *grp;           /* array of groups */
-    int grplen;                        /* length of grp array in elements */
-    struct pat *igcl;          /* array of ignore patterns */
-    int iglen;                 /* length of igcl array in elements */
-{
-    struct grp *gp;            /* current group element being examined */
-    struct pat *pp;            /* current pattern element being examined */
-    int g;                     /* current group index number */
-    int p;                     /* current pattern index number */
-    int ign;                   /* 1 => ignore this group, 0 => check it */
-    int icnt;                  /* groups ignored */
-    int ccnt;                  /* groups to be checked */
-
-    /* firewall */
-    if (grp == NULL)
-        die("internal error #5: grp is NULL");
-    if (igcl == NULL)
-        die("internal error $6: igcl is NULL");
-    if (D_BUG)
-        warn("STATUS: determining which groups to ignore");
-
-    /* if nothing to do, return quickly */
-    if (grplen <= 0 || iglen <= 0) {
-       return;
-    }
-
-    /* examine each group */
-    icnt = 0;
-    ccnt = 0;
-    for (g=0; g < grplen; ++g) {
-
-       /* check the group to examine */
-       gp = &grp[g];
-       if (gp->ignore) {
-           /* already ignored no need to examine */
-           continue;
-       }
-
-       /* check group against all patterns */
-       ign = 0;
-       for (p=0, pp=igcl; p < iglen; ++p, ++pp) {
-
-           /* if pattern has a specific type, check it first */
-           if (pp->type_match) {
-
-               /* specific type required, check for match */
-               switch (gp->type[0]) {
-               case 'y':
-                   if (! pp->y_type) continue;  /* pattern does not apply */
-                   break;
-               case 'm':
-                   if (! pp->m_type) continue;  /* pattern does not apply */
-                   break;
-               case 'n':
-                   if (! pp->n_type) continue;  /* pattern does not apply */
-                   break;
-               case 'j':
-                   if (! pp->j_type) continue;  /* pattern does not apply */
-                   break;
-               case 'x':
-                   if (! pp->x_type) continue;  /* pattern does not apply */
-                   break;
-               case '=':
-                   if (! pp->eq_type) continue;  /* pattern does not apply */
-                   if (pp->epat != NULL && !uwildmat(&gp->type[1], pp->epat)) {
-                       /* equiv pattern doesn't match, patt does not apply */
-                       continue;
-                   }
-                   break;
-               }
-           }
-
-           /* perform a match on group name */
-           if (uwildmat(gp->name, pp->pat)) {
-               /* this pattern fully matches, use the ignore value */
-               ign = pp->ignore;
-           }
-       }
-
-       /* if this group is to be ignored, note it */
-       if (ign) {
-           switch (gp->hostid) {
-           case HOSTID1:
-               if (ign_host1_flag) {
-                   gp->ignore |= CHECK_IGNORE;
-                   ++icnt;
-               }
-               break;
-           case HOSTID2:
-               if (ign_host2_flag) {
-                   gp->ignore |= CHECK_IGNORE;
-                   ++icnt;
-               }
-               break;
-           default:
-                die("newsgroup %s bad hostid: %d", gp->name, gp->hostid);
-           }
-       } else {
-           ++ccnt;
-       }
-    }
-    if (D_BUG)
-        warn("STATUS: examined %d groups: %d ignored, %d to be checked",
-             grplen, icnt, ccnt);
-}
-
-/*
- * merge_cmp - qsort compare function for later group merge
- *
- * given:
- *     a       group a to compare
- *     b       group b to compare
- *
- * returns:
- *     >0      a > b
- *     0       a == b elements match (fatal error if a and b are different)
- *     <0      a < b
- *
- * To speed up group comparison, we compare by the following items listed
- * in order of sorting:
- *
- *     group name
- *     hostid                  (host1 ahead of host2)
- *     linenum                 (active file line number)
- */
-static int
-merge_cmp(arg_a, arg_b)
-    const void *arg_a;         /* first qsort compare arg */
-    const void *arg_b;         /* first qsort compare arg */
-{
-    const struct grp *a = arg_a;       /* group a to compare */
-    const struct grp *b = arg_b;       /* group b to compare */
-    int i;
-
-    /* firewall */
-    if (a == b) {
-       /* we guess this could happen */
-       return(0);
-    }
-
-    /* compare group names */
-    i = strcmp(a->name, b->name);
-    if (i != 0) {
-       return i;
-    }
-
-    /* compare hostid's */
-    if (a->hostid != b->hostid) {
-       if (a->hostid > b->hostid) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* compare active line numbers */
-    if (a->linenum != b->linenum) {
-       if (a->linenum > b->linenum) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* two different elements match, this should not happen! */
-    die("two internal grp elements match!");
-    /*NOTREACHED*/
-}
-
-/*
- * merge_grps - compare groups from both hosts
- *
- * given:
- *     grp     array of groups
- *     grplen  length of grp array in elements
- *     host1   name of host with HOSTID1
- *     host2   name of host with HOSTID2
- *
- * This routine will select which groups to output form a merged active file.
- */
-static void
-merge_grps(grp, grplen, host1, host2)
-    struct grp *grp;           /* array of groups */
-    int grplen;                        /* length of grp array in elements */
-    char *host1;               /* name of host with HOSTID1 */
-    char *host2;               /* name of host with HOSTID2 */
-{
-    int cur;           /* current group index being examined */
-    int nxt;           /* next group index being examined */
-    int outcnt;                /* groups to output */
-    int rmcnt;         /* groups to remove */
-    int h1_probs;      /* =type problem groups from host1 */
-    int h2_probs;      /* =type problem groups from host2 */
-
-    /* firewall */
-    if (grp == NULL)
-        die("internal error #7: grp is NULL");
-
-    /* sort groups for the merge */
-    if (D_BUG)
-        warn("STATUS: sorting groups");
-    qsort((char *)grp, grplen, sizeof(grp[0]), merge_cmp);
-
-    /* mark =type problem groups from host2, if needed */
-    h2_probs = mark_eq_probs(grp, grplen, l_host2_flag, host1, host2);
-
-    /*
-     * We will walk thru the sorted group array, looking for pairs
-     * among the groups that we have not already ignored.
-     *
-     * If a host has duplicate groups, then the duplicates will
-     * be next to each other.
-     *
-     * If both hosts have the name group, they will be next to each other.
-     */
-    if (D_BUG)
-        warn("STATUS: merging groups");
-    outcnt = 0;
-    rmcnt = 0;
-    for (cur=0; cur < grplen; cur=nxt) {
-
-       /* determine the next group index */
-       nxt = cur+1;
-
-       /* skip if this group is ignored */
-       if (grp[cur].ignore) {
-           continue;
-       }
-       /* assert: cur is not ignored */
-
-       /* check for duplicate groups from the same host */
-       while (nxt < grplen) {
-
-           /* mark the later as a duplicate */
-           if (grp[cur].hostid == grp[nxt].hostid &&
-               strcmp(grp[cur].name, grp[nxt].name) == 0) {
-               grp[nxt].ignore |= ERROR_DUP;
-               if (!QUIET(grp[cur].hostid))
-                    warn("lines %d and %d from %s refer to the same group",
-                         grp[cur].linenum, grp[nxt].linenum,
-                         ((grp[cur].hostid == HOSTID1) ? host1 : host2));
-               ++nxt;
-           } else {
-               break;
-           }
-       }
-       /* assert: cur is not ignored */
-       /* assert: cur & nxt are not the same group from the same host */
-
-       /* if nxt is ignored, look for the next non-ignored group */
-       while (nxt < grplen && grp[nxt].ignore) {
-           ++nxt;
-       }
-       /* assert: cur is not ignored */
-       /* assert: nxt is not ignored or is beyond end */
-       /* assert: cur & nxt are not the same group from the same host */
-
-       /* case: cur and nxt are the same group */
-       if (nxt < grplen && strcmp(grp[cur].name, grp[nxt].name) == 0) {
-
-           /* assert: cur is HOSTID1 */
-           if (grp[cur].hostid != HOSTID1)
-                die("internal error #8: grp[%d].hostid: %d != %d",
-                   cur, grp[cur].hostid, HOSTID1);
-
-           /*
-            * Both hosts have the same group.  Make host1 group type
-            * match host2.  (it may already)
-            */
-           grp[cur].output = 1;
-           grp[cur].outhi = (host2_hilow_all ? grp[nxt].hi : grp[cur].hi);
-           grp[cur].outlow = (host2_hilow_all ? grp[nxt].low : grp[cur].low);
-           grp[cur].outtype = grp[nxt].type;
-           ++outcnt;
-
-           /* do not process nxt, skip to the one beyond */
-           ++nxt;
-
-       /* case: cur and nxt are different groups */
-       } else {
-
-           /*
-            * if cur is host2, then host1 doesn't have it, so output it
-            */
-           if (grp[cur].hostid == HOSTID2) {
-               grp[cur].output = 1;
-               grp[cur].outhi = (host2_hilow_newgrp ? grp[cur].hi : DEF_HI);
-               grp[cur].outlow = (host2_hilow_newgrp ? grp[cur].low : DEF_LOW);
-               grp[cur].outtype = grp[cur].type;
-               ++outcnt;
-
-           /*
-            * If cur is host1, then host2 doesn't have it.
-            * Mark for removal if -m was not given.
-            */
-           } else {
-               grp[cur].output = 1;
-               grp[cur].outhi = grp[cur].hi;
-               grp[cur].outlow = grp[cur].low;
-               grp[cur].outtype = grp[cur].type;
-               if (! m_flag) {
-                   grp[cur].remove = 1;
-                   ++rmcnt;
-               }
-           }
-
-           /* if no more groups to examine, we are done */
-           if (nxt >= grplen) {
-               break;
-           }
-       }
-    }
-
-    /* mark =type problem groups from host1, if needed */
-    h1_probs = mark_eq_probs(grp, grplen, l_host1_flag, host1, host2);
-
-    /* all done */
-    if (D_BUG) {
-        warn("STATUS: sort-merge passed thru %d groups", outcnt);
-        warn("STATUS: sort-merge marked %d groups for removal", rmcnt);
-       warn("STATUS: marked %d =type error groups from host1", h1_probs);
-        warn("STATUS: marked %d =type error groups from host2", h2_probs);
-    }
-    return;
-}
-
-/*
- * active_cmp - qsort compare function for active file style output
- *
- * given:
- *     a       group a to compare
- *     b       group b to compare
- *
- * returns:
- *     >0      a > b
- *     0       a == b elements match (fatal error if a and b are different)
- *     <0      a < b
- *
- * This sort will sort groups so that the lines that will we output
- * host1 lines followed by host2 lines.  Thus, we will sort by
- * the following keys:
- *
- *     hostid                  (host1 ahead of host2)
- *     linenum                 (active file line number)
- */
-static int
-active_cmp(arg_a, arg_b)
-    const void *arg_a;         /* first qsort compare arg */
-    const void *arg_b;         /* first qsort compare arg */
-{
-    const struct grp *a = arg_a;       /* group a to compare */
-    const struct grp *b = arg_b;       /* group b to compare */
-
-    /* firewall */
-    if (a == b) {
-       /* we guess this could happen */
-       return(0);
-    }
-
-    /* compare hostid's */
-    if (a->hostid != b->hostid) {
-       if (a->hostid > b->hostid) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* compare active line numbers */
-    if (a->linenum != b->linenum) {
-       if (a->linenum > b->linenum) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* two different elements match, this should not happen! */
-    die("two internal grp elements match!");
-    /*NOTREACHED*/
-}
-
-/*
- * output_grps - output the result of the merge
- *
- * given:
- *     grp     array of groups
- *     grplen  length of grp array in elements
- */
-static void
-output_grps(grp, grplen)
-    struct grp *grp;           /* array of groups */
-    int grplen;                        /* length of grp array in elements */
-{
-    int add;           /* number of groups added */
-    int change;                /* number of groups changed */
-    int remove;                /* number of groups removed */
-    int no_new_dir;    /* number of new groups with missing/empty dirs */
-    int new_dir;       /* number of new groupsm, non-empty dir no water chg */
-    int water_change;  /* number of new groups where hi&low water changed */
-    int work;          /* adds + changes + removals */
-    int same;          /* the number of groups the same */
-    int ignore;                /* host1 newsgroups to ignore */
-    int not_done;      /* exec errors and execs not performed */
-    int rm_cycle;      /* 1 => removals only, 0 => adds & changes only */
-    int sleep_msg;     /* 1 => -o x sleep message was given */
-    int top_ignore;    /* number of groups ignored because of no top level */
-    int restore;       /* host1 groups restored due to -o a1 */
-    double host1_same; /* % of host1 that is the same */
-    int i;
-
-    /* firewall */
-    if (grp == NULL)
-        die("internal error #9: grp is NULL");
-
-    /*
-     * If -a1 was given, mark for output any host1 newsgroup that was
-     * simply ignored due to the -i ign_file.
-     */
-    if (host1_ign_print) {
-       restore = 0;
-       for (i=0; i < grplen; ++i) {
-           if (grp[i].hostid == HOSTID1 && 
-               (grp[i].ignore == CHECK_IGNORE ||
-                grp[i].ignore == CHECK_TYPE ||
-                grp[i].ignore == (CHECK_IGNORE|CHECK_TYPE))) {
-               /* force group to output and not be ignored */
-               grp[i].ignore = 0;
-               grp[i].output = 1;
-               grp[i].remove = 0;
-               grp[i].outhi = grp[i].hi;
-               grp[i].outlow = grp[i].low;
-               grp[i].outtype = grp[i].type;
-               ++restore;
-           }
-       }
-       if (D_BUG)
-            warn("STATUS: restored %d host1 groups", restore);
-    }
-
-    /*
-     * If -T, ignore new top level groups from host2
-     */
-    if (no_new_hier) {
-       top_ignore = 0;
-       for (i=0; i < grplen; ++i) {
-           /* look at new newsgroups */
-           if (grp[i].hostid == HOSTID2 &&
-               grp[i].output != 0 &&
-               new_top_hier(grp[i].name)) {
-                /* no top level ignore this new group */
-                grp[i].ignore |= CHECK_HIER;
-                grp[i].output = 0;
-                if (D_BUG)
-                     warn("ignore new newsgroup: %s, new hierarchy",
-                          grp[i].name);
-                ++top_ignore;
-           }
-       }
-       if (D_SUMMARY)
-            warn("STATUS: ignored %d new newsgroups due to new hierarchy",
-                 top_ignore);
-    }
-
-    /* sort by active file order if active style output (-a) */
-    if (o_flag == OUTPUT_ACTIVE) {
-       if (D_BUG)
-            warn("STATUS: sorting groups in output order");
-       qsort((char *)grp, grplen, sizeof(grp[0]), active_cmp);
-    }
-
-    /*
-     * Determine the % of lines from host1 active file that remain unchanged
-     * ignoring any low/high water mark changes.
-     *
-     * Determine the number of old groups that will remain the same
-     * the number of new groups that will be added.
-     */
-    add = 0;
-    change = 0;
-    remove = 0;
-    same = 0;
-    ignore = 0;
-    no_new_dir = 0;
-    new_dir = 0;
-    water_change = 0;
-    for (i=0; i < grplen; ++i) {
-       /* skip non-output ...  */
-       if (grp[i].output == 0) {
-           if (grp[i].hostid == HOSTID1) {
-               ++ignore;
-           }
-           continue;
-
-       /* case: group needs removal */
-       } else if (grp[i].remove) {
-           ++remove;
-
-       /* case: group is from host2, so we need a newgroup */
-       } else if (grp[i].hostid == HOSTID2) {
-           ++add;
-
-       /* case: group is from host1, but the type changed */
-       } else if (grp[i].type != grp[i].outtype &&
-                  strcmp(grp[i].type,grp[i].outtype) != 0) {
-           ++change;
-
-       /* case: group did not change */
-       } else {
-           ++same;
-       }
-    }
-    work = add+change+remove;
-    if (same+work+host1_errs <= 0) {
-       /* no lines, no work, no errors == nothing changed == 100% the same */
-       host1_same = (double)100.0;
-    } else {
-       /* calculate % unchanged */
-       host1_same = (double)100.0 *
-                    ((double)same / (double)(same+work+host1_errs));
-    }
-    if (D_BUG) {
-        warn("STATUS: same=%d add=%d, change=%d, remove=%d",
-             same, add, change, remove);
-        warn("STATUS: ignore=%d, work=%d, err=%d",
-             ignore, work, host1_errs);
-        warn("STATUS: same+work+err=%d, host1_same=%.2f%%",
-             same+work+host1_errs, host1_same);
-    }
-
-    /* 
-     * Bail out if we too few lines in host1 active file (ignoring
-     * low/high water mark changes) remaining unchanged.
-     *
-     * We define change as:
-     *
-     * line errors from host1 active file
-     * newsgroups to be added to host1
-     * newsgroups to be removed from host1
-     * newsgroups to be change in host1
-     */
-    if (host1_same < p_flag) {
-        warn("HALT: lines unchanged: %.2f%% < min change limit: %.2f%%",
-             host1_same, p_flag);
-        warn("    No output or commands executed.  Determine if the degree");
-        warn("    of changes is okay and re-execute with a lower -p value");
-        die("    or with the problem fixed.");
-    }
-
-    /*
-     * look at all groups
-     *
-     * If we are not producing active file output, we must do removals
-     * before we do any adds and changes.
-     *
-     * We recalculate the work stats in finer detail as well as noting how
-     * many actions were successful.
-     */
-    add = 0;
-    change = 0;
-    remove = 0;
-    same = 0;
-    ignore = 0;
-    work = 0;
-    not_done = 0;
-    sleep_msg = 0;
-    rm_cycle = ((o_flag == OUTPUT_ACTIVE) ? 0 : 1);
-    do {
-       for (i=0; i < grplen; ++i) {
-
-           /* if -o Ax, output ignored non-error groups too */
-
-           /*
-            * skip non-output ...
-            *
-            * but if '-a' and active output mode, then don't skip ignored,
-            * non-error, non-removed groups from host1
-            */
-           if (grp[i].output == 0) {
-               if (grp[i].hostid == HOSTID1) {
-                   ++ignore;
-               }
-               continue;
-           }
-
-           /* case: output active lines */
-           if (o_flag == OUTPUT_ACTIVE) {
-
-               /* case: group needs removal */
-               if (grp[i].remove) {
-                   ++remove;
-                   ++work;
-
-               /* case: group will be kept */
-               } else {
-
-                   /* output in active file format */
-                   printf("%s %s %s %s\n",
-                       grp[i].name,  grp[i].outhi, grp[i].outlow,
-                       grp[i].outtype);
-
-                   /* if -v level is high enough, do group accounting */
-                   if (D_IF_SUMM) {
-
-                       /* case: group is from host2, so we need a newgroup */
-                       if (grp[i].hostid == HOSTID2) {
-                           ++add;
-                           ++work;
-
-                       /* case: group is from host1, but the type changed */
-                       } else if (grp[i].type != grp[i].outtype &&
-                                  strcmp(grp[i].type,grp[i].outtype) != 0) {
-                           ++change;
-                           ++work;
-
-                       /* case: group did not change */
-                       } else {
-                           ++same;
-                       }
-                   }
-               }
-
-           /* case: output ctlinnd commands */
-           } else if (o_flag == OUTPUT_CTLINND) {
-
-               /* case: group needs removal */
-               if (grp[i].remove) {
-
-                   /* output rmgroup */
-                   if (rm_cycle) {
-                       printf("ctlinnd rmgroup %s\n", grp[i].name);
-                       ++remove;
-                       ++work;
-                   }
-
-               /* case: group is from host2, so we need a newgroup */
-               } else if (grp[i].hostid == HOSTID2) {
-
-                   /* output newgroup */
-                   if (! rm_cycle) {
-                       printf("ctlinnd newgroup %s %s %s\n",
-                           grp[i].name, grp[i].outtype, new_name);
-                       ++add;
-                       ++work;
-                   }
-
-               /* case: group is from host1, but the type changed */
-               } else if (grp[i].type != grp[i].outtype &&
-                          strcmp(grp[i].type,grp[i].outtype) != 0) {
-
-                   /* output changegroup */
-                   if (! rm_cycle) {
-                       printf("ctlinnd changegroup %s %s\n",
-                           grp[i].name, grp[i].outtype);
-                       ++change;
-                       ++work;
-                   }
-
-               /* case: group did not change */
-               } else {
-                   if (! rm_cycle) {
-                       ++same;
-                   }
-               }
-
-           /* case: exec ctlinnd commands */
-           } else if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) {
-
-               /* warn about sleeping if needed and first time */
-               if (o_flag == OUTPUT_EXEC && z_flag > 0 && sleep_msg == 0) {
-                   if (D_SUMMARY)
-                        warn("will sleep %d seconds before each fork/exec",
-                             z_flag);
-                   sleep_msg = 1;
-               }
-
-               /* case: group needs removal */
-               if (grp[i].remove) {
-
-                   /* exec rmgroup */
-                   if (rm_cycle) {
-                       if (D_REPORT && o_flag == OUTPUT_EXEC)
-                            warn("rmgroup %s", grp[i].name);
-                       if (! exec_cmd(o_flag, "rmgroup",
-                           grp[i].name, NULL, NULL)) {
-                           ++not_done;
-                       } else {
-                           ++remove;
-                           ++work;
-                       }
-                   }
-
-               /* case: group is from host2, so we need a newgroup */
-               } else if (grp[i].hostid == HOSTID2) {
-
-                   /* exec newgroup */
-                   if (!rm_cycle) {
-                       if (D_REPORT && o_flag == OUTPUT_EXEC)
-                            warn("newgroup %s %s %s",
-                                 grp[i].name, grp[i].outtype, new_name);
-                       if (! exec_cmd(o_flag, "newgroup", grp[i].name,
-                                grp[i].outtype, new_name)) {
-                           ++not_done;
-                       } else {
-                           ++add;
-                           ++work;
-                       }
-                   }
-
-               /* case: group is from host1, but the type changed */
-               } else if (grp[i].type != grp[i].outtype &&
-                          strcmp(grp[i].type,grp[i].outtype) != 0) {
-
-                   /* exec changegroup */
-                   if (!rm_cycle) {
-                       if (D_REPORT && o_flag == OUTPUT_EXEC)
-                            warn("changegroup %s %s",
-                                 grp[i].name, grp[i].outtype);
-                       if (! exec_cmd(o_flag, "changegroup", grp[i].name,
-                                grp[i].outtype, NULL)) {
-                           ++not_done;
-                       } else {
-                           ++change;
-                           ++work;
-                       }
-                   }
-
-               /* case: group did not change */
-               } else {
-                   if (! rm_cycle) {
-                       ++same;
-                   }
-               }
-           }
-       }
-    } while (--rm_cycle >= 0);
-
-    /* final accounting, if -v */
-    if (D_SUMMARY || (D_IF_SUMM && (work > 0 || not_done > 0))) {
-        warn("STATUS: %d group(s)", add+remove+change+same);
-        warn("STATUS: %d group(s)%s added", add,
-             ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
-              "" : " to be"));
-        warn("STATUS: %d group(s)%s removed",  remove,
-             ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
-              "" : " to be"));
-        warn("STATUS: %d group(s)%s changed", change,
-             ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
-              "" : " to be"));
-        warn("STATUS: %d group(s) %s the same", same,
-             ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
-              "remain" : "are"));
-        warn("STATUS: %.2f%% of lines unchanged", host1_same);
-        warn("STATUS: %d group(s) ignored", ignore);
-       if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC)
-            warn("STATUS: %d exec(s) not performed", not_done);
-    }
-}
-
-/*
- * error_mark - mark for removal, error groups from a given host
- *
- * given:
- *     grp     array of groups
- *     grplen  length of grp array in elements
- *     hostid  host to mark error groups for removal
- */
-static void
-error_mark(grp, grplen, hostid)
-    struct grp *grp;           /* array of groups */
-    int grplen;                        /* length of grp array in elements */
-    int hostid;                        /* host to mark error groups for removal */
-{
-    int i;
-    int errcnt;
-
-    /* firewall */
-    if (grp == NULL)
-        die("internal error #11: grp is NULL");
-
-    /* loop thru groups, looking for error groups from a given host */
-    errcnt = 0;
-    for (i=0; i < grplen; ++i) {
-
-       /* skip if not from hostid */
-       if (grp[i].hostid != hostid) {
-           continue;
-       }
-
-       /* mark for removal if an error group not already removed */
-       if (IS_ERROR(grp[i].ignore)) {
-
-           /* mark for removal */
-           if (grp[i].output != 1 || grp[i].remove != 1) {
-               grp[i].output = 1;
-               grp[i].remove = 1;
-           }
-           ++errcnt;
-       }
-    }
-
-    /* all done */
-    if (D_SUMMARY || (D_IF_SUMM && errcnt > 0))
-        warn("STATUS: marked %d error groups for removal", errcnt);
-    return;
-}
-
-/*
- * eq_merge_cmp - qsort compare function for =type group processing
- *
- * given:
- *     a       =group a to compare
- *     b       =group b to compare
- *
- * returns:
- *     >0      a > b
- *     0       a == b elements match (fatal error if a and b are different)
- *     <0      a < b
- *
- * To speed up group comparison, we compare by the following items listed
- * in order of sorting:
- *
- *     skip                    (non-skipped groups after skipped ones)
- *     group equiv name
- *     group name
- *     hostid                  (host1 ahead of host2)
- *     linenum                 (active file line number)
- */
-static int
-eq_merge_cmp(arg_a, arg_b)
-    const void *arg_a;         /* first qsort compare arg */
-    const void *arg_b;         /* first qsort compare arg */
-{
-    const struct eqgrp *a = arg_a;     /* group a to compare */
-    const struct eqgrp *b = arg_b;     /* group b to compare */
-    int i;
-
-    /* firewall */
-    if (a == b) {
-       /* we guess this could happen */
-       return(0);
-    }
-
-    /* compare skip values */
-    if (a->skip != b->skip) {
-       if (a->skip > b->skip) {
-           /* a is skipped, b is not */
-           return 1;
-       } else {
-           /* b is skipped, a is not */
-           return -1;
-       }
-    }
-
-    /* compare the names the groups are equivalenced to */
-    i = strcmp(a->eq, b->eq);
-    if (i != 0) {
-       return i;
-    }
-
-    /* compare the group names themselves */
-    i = strcmp(a->g->name, b->g->name);
-    if (i != 0) {
-       return i;
-    }
-
-    /* compare hostid's */
-    if (a->g->hostid != b->g->hostid) {
-       if (a->g->hostid > b->g->hostid) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* compare active line numbers */
-    if (a->g->linenum != b->g->linenum) {
-       if (a->g->linenum > b->g->linenum) {
-           return 1;
-       } else {
-           return -1;
-       }
-    }
-
-    /* two different elements match, this should not happen! */
-    die("two internal eqgrp elements match!");
-}
-
-/*
- * mark_eq_probs - mark =type groups from a given host that have problems
- *
- * given:
- *     grp      sorted array of groups
- *     grplen   length of grp array in elements
- *     hostid   host to mark error groups for removal, or NOHOST
- *     host1   name of host with HOSTID1
- *     host2   name of host with HOSTID2
- *
- * This function assumes that the grp array has been sorted by name.
- */
-static int
-mark_eq_probs(grp, grplen, hostid, host1, host2)
-    struct grp *grp;           /* array of groups */
-    int grplen;                        /* length of grp array in elements */
-    int hostid;                        /* host to mark error groups for removal */
-    char *host1;               /* name of host with HOSTID1 */
-    char *host2;               /* name of host with HOSTID2 */
-{
-    struct eqgrp *eqgrp;       /* =type pointer array */
-    int eq_cnt;                        /* number of =type groups from host */
-    int new_eq_cnt;            /* number of =type groups remaining */
-    int missing;               /* =type groups equiv to missing groups */
-    int cycled;                        /* =type groups equiv to themselves */
-    int chained;               /* =type groups in long chain or loop */
-    int cmp;                   /* strcmp of two names */
-    int step;                  /* equiv loop step */
-    int i;
-    int j;
-
-    /* firewall */
-    if (grp == NULL)
-        die("internal error #12: grp is NULL");
-    if (hostid == NOHOST) {
-       /* nothing to detect, nothing else to do */
-       return 0;
-    }
-
-    /* count the =type groups from hostid that are not in error */
-    eq_cnt = 0;
-    for (i=0; i < grplen; ++i) {
-       if (grp[i].hostid == hostid &&
-           ! IS_ERROR(grp[i].ignore) &&
-           grp[i].type != NULL &&
-           grp[i].type[0] == '=') {
-           ++eq_cnt;
-       }
-    }
-    if (D_BUG && hostid != NOHOST)
-        warn("STATUS: host%d has %d =type groups", hostid, eq_cnt);
-
-    /* if no groups, then there is nothing to do */
-    if (eq_cnt == 0) {
-       return 0;
-    }
-
-    /* setup the =group record array */
-    eqgrp = xmalloc(eq_cnt * sizeof(eqgrp[0]));
-    for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
-       if (grp[i].hostid == hostid &&
-           ! IS_ERROR(grp[i].ignore) &&
-           grp[i].type != NULL &&
-           grp[i].type[0] == '=') {
-
-           /* initialize record */
-           eqgrp[j].skip = 0;
-           eqgrp[j].g = &grp[i];
-           eqgrp[j].eq = &(grp[i].type[1]);
-           ++j;
-       }
-    }
-
-    /*
-     * try to resolve =type groups in at least EQ_LOOP equiv links
-     */
-    new_eq_cnt = eq_cnt;
-    missing = 0;
-    cycled = 0;
-    for (step=0; step < EQ_LOOP && new_eq_cnt >= 0; ++step) {
-
-       /* sort the =group record array */
-       qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
-
-       /* look for the groups to which =type group point at */
-       eq_cnt = new_eq_cnt;
-       for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
-
-           /* we will skip any group in error or from the wrong host */
-           if (grp[i].hostid != hostid || IS_ERROR(grp[i].ignore)) {
-               continue;
-           }
-
-           /* we will skip any skipped eqgrp's */
-           if (eqgrp[j].skip) {
-               /* try the same group against the next eqgrp */
-               --i;
-               ++j;
-               continue;
-           }
-
-           /* compare the =name of the eqgrp with the name of the grp */
-           cmp = strcmp(grp[i].name, eqgrp[j].eq);
-
-           /* case: this group is pointed at by an eqgrp */
-           if (cmp == 0) {
-
-                /* see if we have looped around to the original group name */
-                if (strcmp(grp[i].name, eqgrp[j].g->name) == 0) {
-
-                   /* note the detected loop */
-                   if (! QUIET(hostid))
-                        warn("%s from %s line %d =loops around to itself",
-                             eqgrp[j].g->name,
-                             ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
-                             eqgrp[j].g->linenum);
-                    eqgrp[j].g->ignore |= ERROR_EQLOOP;
-
-                   /* the =group is bad, so we don't need to bother with it */
-                   eqgrp[j].skip = 1;
-                   --new_eq_cnt;
-                   ++cycled;
-                   --i;
-                   ++j;
-                   continue;
-               }
-
-               /* if =group refers to a valid group, we are done with it */
-               if (grp[i].type != NULL && grp[i].type[0] != '=') {
-                   eqgrp[j].skip = 1;
-                   --new_eq_cnt;
-               /* otherwise note the equiv name */
-               } else {
-                   eqgrp[j].eq = &(grp[i].type[1]);
-               }
-               --i;
-               ++j;
-
-           /* case: we missed the =name */
-           } else if (cmp > 0) {
-
-               /* mark the eqgrp in error */
-               eqgrp[j].g->ignore |= ERROR_NONEQ;
-               if (! QUIET(hostid))
-                    warn("%s from %s line %d not equiv to a valid group",
-                         eqgrp[j].g->name,
-                         ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
-                         eqgrp[j].g->linenum);
-
-               /* =group is bad, so we don't need to bother with it anymore */
-               eqgrp[j].skip = 1;
-               --new_eq_cnt;
-               ++missing;
-               ++j;
-           }
-       }
-
-       /* any remaining non-skipped eqgrps are bad */
-       while (j < eq_cnt) {
-
-           /* mark the eqgrp in error */
-           eqgrp[j].g->ignore |= ERROR_NONEQ;
-           if (! QUIET(hostid))
-                warn("%s from %s line %d isn't equiv to a valid group",
-                     eqgrp[j].g->name,
-                     ((hostid == HOSTID1) ? host1 : host2),
-                     eqgrp[j].g->linenum);
-
-           /* the =group is bad, so we don't need to bother with it anymore */
-           eqgrp[j].skip = 1;
-           --new_eq_cnt;
-           ++missing;
-           ++j;
-       }
-    }
-
-    /* note groups that are in a long chain or loop */
-    chained = new_eq_cnt;
-    qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
-    for (j=0; j < new_eq_cnt; ++j) {
-
-       /* skip if already skipped */
-       if (eqgrp[j].skip == 1) {
-           continue;
-       }
-
-       /* mark as a long loop group */
-       eqgrp[j].g->ignore |= ERROR_LONGLOOP;
-       if (! QUIET(hostid))
-            warn("%s from %s line %d in a long equiv chain or loop > %d",
-                 eqgrp[j].g->name,
-                 ((hostid == HOSTID1) ? host1 : host2),
-                 eqgrp[j].g->linenum, EQ_LOOP);
-    }
-
-    /* all done */
-    if (D_BUG) {
-        warn("%d =type groups from %s are not equiv to a valid group",
-             missing, ((hostid == HOSTID1) ? host1 : host2));
-        warn("%d =type groups from %s are equiv to themselves",
-             cycled, ((hostid == HOSTID1) ? host1 : host2));
-        warn("%d =type groups from %s are in a long chain or loop > %d",
-             chained, ((hostid == HOSTID1) ? host1 : host2), EQ_LOOP);
-    }
-    free(eqgrp);
-    return missing+cycled+chained;
-}
-
-/*
- * exec_cmd - exec a ctlinnd command in forked process
- *
- * given:
- *     mode    OUTPUT_EXEC or OUTPUT_IEXEC (interactive mode)
- *     cmd     "changegroup", "newgroup", "rmgroup"
- *     grp     name of group
- *     type    type of group or NULL
- *     who     newgroup creator or NULL
- *
- * returns:
- *     1       exec was performed
- *     0       exec was not performed
- */
-static int
-exec_cmd(mode, cmd, grp, type, who)
-    int mode;          /* OUTPUT_EXEC or OUTPUT_IEXEC (interactive mode) */
-    char *cmd;         /* changegroup, newgroup or rmgroup */
-    char *grp;         /* name of group to change, add, remove */
-    char *type;                /* type of group or NULL */
-    char *who;         /* newgroup creator or NULL */
-{
-    FILE *ch_stream = NULL;    /* stream from a child process */
-    char buf[BUFSIZ+1];                /* interactive buffer */
-    int pid;                   /* pid of child process */
-    int io[2];                 /* pair of pipe descriptors */
-    int status;                        /* wait status */
-    int exitval;               /* exit status of the child */
-    char *p;
-
-    /* firewall */
-    if (cmd == NULL || grp == NULL)
-        die("internal error #13, cmd or grp is NULL");
-
-    /* if interactive, ask the question */
-    if (mode == OUTPUT_IEXEC) {
-
-       /* ask the question */
-       fflush(stdin);
-       fflush(stdout);
-       fflush(stderr);
-       if (type == NULL) {
-           printf("%s %s  [yn]? ", cmd, grp);
-       } else if (who == NULL) {
-           printf("%s %s %s  [yn]? ", cmd, grp, type);
-       } else {
-           printf("%s %s %s %s  [yn]? ", cmd, grp, type, who);
-       }
-       fflush(stdout);
-       buf[0] = '\0';
-       buf[BUFSIZ] = '\0';
-       p = fgets(buf, BUFSIZ, stdin);
-       if (p == NULL) {
-           /* EOF/ERROR on interactive input, silently stop processing */
-           exit(43);
-       }
-
-       /* if non-empty line doesn't start with 'y' or 'Y', skip command */
-       if (buf[0] != 'y' && buf[0] != 'Y' && buf[0] != '\n') {
-           /* indicate nothing was done */
-           return 0;
-       }
-    }
-
-    /* build a pipe for output from child interactive mode */
-    if (mode == OUTPUT_IEXEC) {
-       if (pipe(io) < 0)
-            sysdie("pipe create failed");
-
-    /* setup a fake pipe to /dev/null for non-interactive mode */
-    } else {
-       io[READ_SIDE] = open(DEV_NULL, 0);
-       if (io[READ_SIDE] < 0)
-            sysdie("unable to open %s for reading", DEV_NULL);
-       io[WRITE_SIDE] = open(DEV_NULL, 1);
-       if (io[WRITE_SIDE] < 0)
-            sysdie("unable to open %s for writing", DEV_NULL);
-    }
-
-    /* pause if in non-interactive mode so as to not busy-out the server */
-    if (mode == OUTPUT_EXEC && z_flag > 0) {
-       if (D_BUG)
-            warn("sleeping %d seconds before fork/exec", z_flag);
-           /* be sure they know what we are stalling */
-           fflush(stderr);
-       sleep(z_flag);
-    }
-
-    /* fork the child process */
-    fflush(stdout);
-    fflush(stderr);
-    pid = fork();
-    if (pid == -1)
-        sysdie("fork failed");
-
-    /* case: child process */
-    if (pid == 0) {
-
-       /*
-        * prep file descriptors
-        */
-       fclose(stdin);
-       close(io[READ_SIDE]);
-       if (dup2(io[WRITE_SIDE], 1) < 0)
-            sysdie("child: dup of write I/O pipe to stdout failed");
-       if (dup2(io[WRITE_SIDE], 2) < 0)
-            sysdie("child: dup of write I/O pipe to stderr failed");
-
-       /* exec the ctlinnd command */
-       p = concatpath(innconf->pathbin, _PATH_CTLINND);
-       if (type == NULL) {
-           execl(p,
-                 CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, (char *) 0);
-       } else if (who == NULL) {
-           execl(p,
-                 CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, type, (char *) 0);
-       } else {
-           execl(p,
-                 CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, type, who, (char *) 0);
-       }
-
-       /* child exec failed */
-        sysdie("child process exec failed");
-
-    /* case: parent process */
-    } else {
-
-       /* prep file descriptors */
-       if (mode != OUTPUT_IEXEC) {
-           close(io[READ_SIDE]);
-       }
-       close(io[WRITE_SIDE]);
-
-       /* print a line from the child, if interactive */
-       if (mode == OUTPUT_IEXEC) {
-
-           /* read what the child says */
-           buf[0] = '\0';
-           buf[BUFSIZ] = '\0';
-           ch_stream = fdopen(io[READ_SIDE], "r");
-           if (ch_stream == NULL)
-                sysdie("fdopen of pipe failed");
-           p = fgets(buf, BUFSIZ, ch_stream);
-
-           /* print what the child said, if anything */
-           if (p != NULL) {
-               if (buf[strlen(buf)-1] == '\n')
-                    buf[strlen(buf)-1] = '\0';
-                warn("    %s", buf);
-           }
-       }
-
-       /* look for abnormal child termination/status */
-       errno = 0;
-       while (wait(&status) < 0) {
-           if (errno == EINTR) {
-               /* just an interrupt, try to wait again */
-               errno = 0;
-           } else {
-                sysdie("wait returned -1");
-           }
-       }
-       if (mode == OUTPUT_IEXEC) {
-           /* close the pipe now that we are done with reading it */
-           fclose(ch_stream);
-       }
-       if (WIFSTOPPED(status)) {
-            warn("    %s %s %s%s%s%s%s stopped",
-                 CTLINND_NAME, cmd, grp,
-                 (type ? "" : " "), (type ? type : ""),
-                 (who ? "" : " "), (who ? who : ""));
-           /* assume no work was done */
-           return 0;
-       }
-       if (WIFSIGNALED(status)) {
-            warn("    %s %s %s%s%s%s%s killed by signal %d",
-                 CTLINND_NAME, cmd, grp,
-                 (type ? "" : " "), (type ? type : ""),
-                 (who ? "" : " "), (who ? who : ""), WTERMSIG(status));
-           /* assume no work was done */
-           return 0;
-       }
-       if (!WIFEXITED(status)) {
-            warn("    %s %s %s%s%s%s%s returned unknown wait status: 0x%x",
-                 CTLINND_NAME, cmd, grp,
-                 (type ? "" : " "), (type ? type : ""),
-                 (who ? "" : " "), (who ? who : ""), status);
-           /* assume no work was done */
-           return 0;
-       }
-       exitval = WEXITSTATUS(status);
-       if (exitval != 0) {
-            warn("    %s %s %s%s%s%s%s exited with status: %d",
-                 CTLINND_NAME, cmd, grp,
-                 (type ? "" : " "), (type ? type : ""),
-                 (who ? "" : " "), (who ? who : ""), exitval);
-           /* assume no work was done */
-           return 0;
-       }
-    }
-
-    /* all done */
-    return 1;
-}
-
-/*
- * new_top_hier - determine if the newsgroup represents a new hierarchy
- *
- * Determine of the newsgroup name is a new hierarchy.
- *
- * given:
- *     name    name of newsgroup to check
- *
- * returns:
- *     false   hierarchy already exists
- *     true    hierarchy does not exist, name represents a new hierarchy
- *
- * NOTE: This function assumes that we are at the top of the news spool.
- */
-static int
-new_top_hier(name)
-    char *name;
-{
-    struct stat        statbuf;        /* stat of the hierarchy */
-    int result;                        /* return result */
-    char *dot;
-
-    /*
-     * temp change name to just the top level
-     */
-    dot = strchr(name, '.');
-    if (dot != NULL) {
-       *dot = '\0';
-    }
-
-    /*
-     * determine if we can find this top level hierarchy directory
-     */
-    result = !(stat(name, &statbuf) >= 0 && S_ISDIR(statbuf.st_mode));
-    /* restore name */
-    if (dot != NULL) {
-       *dot = '.';
-    }
-
-    /*
-     * return the result
-     */
-    return result;
-}