/* @(#) $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 #include #include #include #include #include #include #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] pattern [ 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; }