/* @(#) $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;
}
syntax highlighted by Code2HTML, v. 0.9.1