+++ /dev/null
-/* @(#) $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;
-}