[net.sources] Re-post of nbatcher - a news batching program

espo@bpa.BELL-ATL.COM (Bob Esposito) (12/06/86)

	This is a re-posting of the original nbatcher source.
	There have been fixes applied that don't clobber newly
	arrived news articles in the batch files.  A -v option
	(for verbose operation), and a -u option (for displaying
	no. of bytes on the uucp queue).


	Enjoy,

        Bob Esposito - espo@bpa.bell-atl.com

------------------------cut-here----------------------------------------
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	define.c
#	main.c
#	parse.c
#	checkwork.c
#	nbatcher.c
#	logger.c
#	nbatcher.h
#	bst.c
#	copy.c
#	README
#	nbatcher.ctl
#	nbatcher.doc
#	nbatcher.1
#	Makefile
echo x - extracting define.c
sed 's/^X//' << 'SHAR_EOF' > define.c
X/*******************************************************
X *	define.c - global defines for nbatcher.
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ******************************************************/
X
X#include <stdio.h>
X#include "nbatcher.h"
X
XFILE	*lfp,
X	*tfp,
X	*log = NULL;
X
Xlong	n_bytes,
X	cu_bytes;
X
Xchar	*tfile = NULL;
X
Xshort	vflg = 0,
X	encode = 0,
X	c_opt = 0,
X	u_opt = 0,
X	nfiles = 10;
X
Xint	fcnt = 0,
X	scnt = 0;
X
SHAR_EOF
echo x - extracting main.c
sed 's/^X//' << 'SHAR_EOF' > main.c
Xchar	version[] = "nbatcher 2.0 8/21/86";
X
X/********************************************
X *
X *	main.c - for nbatcher
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ********************************************/
X
X#include <stdio.h>
X#include "nbatcher.h"
X
Xmain(argc, argv)
Xchar **argv;
X{
X	int	uid, nowork, work_to_do();
X	FILE	*cfp;
X	char	fbuf[BUFSIZ];
X
X	uid = getuid();
X
X	if (!strcmp(argv[1], "-c"))
X		c_opt = TRUE;
X	else if (!strcmp(argv[1], "-u"))
X		u_opt = TRUE;
X
X	if (uid && uid != NEWSUID && !u_opt)
X		xerror ("permission denied - not NEWSUSER\n");
X
X	if (chdir(LIBDIR) < 0)
X		xerror ("can't chdir to %s\n", LIBDIR);
X
X	if ((cfp=fopen("nbatcher.ctl", "r")) == NULL)
X		xerror ("no `nbatcher.ctl' file found\n");
X
X	if (isatty(0)) {
X		vflg = TRUE;
X		(void) fprintf(stderr, "%s\n\n", version);
X		if (c_opt == TRUE)
X			(void) fprintf(stderr, "checking control file.... ");
X	}
X
X	nowork = TRUE;
X	while ((fgets(fbuf, sizeof(fbuf), cfp)) != NULL) {
X		if (fbuf[0] == '*' || fbuf[0] == '\n')
X			continue;
X		parse_entry (fbuf);
X		if (c_opt)
X			continue;
X		else if (u_opt) {
X			check_uucp();
X			continue;
X		}
X		if (!work_to_do())
X			continue;
X		batch_it ();
X		nowork = FALSE;
X	}
X
X	fclose (cfp);
X	if (c_opt) {
X		(void) fprintf(stderr, "things appear to be OK\n");
X		exit (0);
X	}
X	fclose (tfp);
X	unlink (tfile);
X	if (vflg == TRUE && nowork == TRUE && !u_opt)
X		(void) fprintf(stderr, "no work to do\n");
X	exit (0);
X}
X	
SHAR_EOF
echo x - extracting parse.c
sed 's/^X//' << 'SHAR_EOF' > parse.c
X/****************************************************************
X *
X *	parse.c - nbatcher line parser for the control file
X *
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ****************************************************************/
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <ctype.h>
X#include "nbatcher.h"
X
X#define MAX_BYTES	1000000L	/* max allowable bytes */
X
Xparse_entry (line)
Xchar *line;
X{
X	register char	*p;
X	short	num, upper;
X	short	lower, dash;
X	long	l_num;
X
X	upper = 23;	/* upper hour limit */
X	lower = 0;	/* lower hour limit */
X	dash = 0;
X
X	clear_entry (&ep);	/* zero out the structure */
X
X	p = (char *) ep.site;
X
X	/* get the site name and copy
X	   it to the structure */
X
X	while (*line && *line != COLON)
X		*p++ = *line++;
X	*p = '\0';
X	if (*++line == '\n' || *line == '\0')
X		xerror ("illegal number of fields for %s\n", ep.site);
X
X	/* check that its valid */
X
X	if (ep.site[0] == '\0')
X		xerror ("null site name in control file for %s\n", ep.site);
X
X	/* now, parse the hour string and check
X	   for valid syntax */
X
X	p = (char *) ep.hour;
X	while (*line && *line != COLON)
X		*p++ = *line++;
X
X	*p = '\0';
X	if (*++line == '\n' || *line == '\0')
X		xerror ("illegal number of fields for %s\n", ep.site);
X
X	if (ep.hour[0] == '\0')
X		xerror ("null hour string in control file for %s\n", ep.site);
X
X	/* now re-scan the hour in structure and
X	   weed out the badies */
X
X	if (ep.hour[0] == '*' && ep.hour[1] != '\0')
X		xerror ("invalid hour string syntax for %s: %s\n", ep.site,
X			ep.hour);
X	else if (ep.hour[0] == '*')
X		goto h_skip;
X
X	if (strcmp(ep.hour, "off", 3) == 0 && ep.hour[3] != '\0')
X		xerror ("invalid hour string syntax for %s: %s\n", ep.site,
X			ep.hour);
X	else if (strncmp(ep.hour, "off", 3) == 0)
X		goto h_skip;
X
X	p = (char *) ep.hour;
X	if (!isdigit(*p))
X		xerror ("non-numeric char in hour string for %s: %c\n", ep.site, *p);
X	while (*p) {
X		num = 0;
X		do {
X			num = num*10 + (*p - '0');
X		} while (isdigit(*++p));
X
X		if (num < lower || num > upper)
X			xerror ("illegal hour for %s: %d\n", ep.site, num);
X
X		if (!*p)
X			break;
X
X		if (*p == '-' && dash)
X			xerror ("syntax error in hour field for %s\n",ep.site);
X		else if (*p == '-')
X			dash = TRUE;
X
X		if (*p != ',' && *p != '-')
X			xerror ("non-numeric char in hour string for %s: %c\n", ep.site, *p);
X		else if (!isdigit(*++p))
X			xerror ("syntax error in hour field for %s\n",ep.site);
X
X	}
X
X	/* now that thats over with, let do the compression
X	   field.  Only 9-16 is allowed, except a null field
X	   indicates no compression for this site.  Allow for
X	   the char `e' to indicate encoding. */
X
Xh_skip:
X	num = 0;
X	while (*line && *line != COLON) {
X		if (*line == 'e') {
X			encode = TRUE;
X			line++;
X			continue;
X		}
X		if (!isdigit(*line))
X			xerror ("illegal compression field syntax for %s\n",
X				ep.site);
X		num = num*10 + (*line++ - '0');
X	}
X	if (*++line == '\n' || *line == '\0')
X		xerror ("illegal number of fields for %s\n", ep.site);
X
X	if (num != 0 && (num < 9 || num > 16))
X		xerror ("illegal compression bits for %s: %d\n", ep.site,
X			num);
X	ep.c_bits = num;
X
X	/* now check the max. bytes for UUCP queue.
X	   Note: There is a max. allowable # of bytes
X		 here, set at 1MB.  Change it at your
X		 own risk.
X	*/
X
X	l_num = 0;
X	while (*line && *line != COLON) {
X		if (!isdigit(*line))
X			xerror ("non-numeric in max. bytes field for %s\n",
X				ep.site);
X		l_num = l_num*10 + (*line++ - '0');
X	}
X
X	if (l_num > MAX_BYTES)
X		xerror ("%ld max. bytes exceeds allowable maximum for %s\n", l_num, ep.site);
X
X	if (l_num != 0)
X		ep.m_bytes = l_num;
X	else
X		ep.m_bytes = DFL_BYTES;
X
X	/* and finally the command line (if there is one) */
X
X	p = (char *) ep.command;
X
X	if (*++line != '\n' && *line != '\0') {
X		while (*line && *line != '\n')
X			*p++ = *line++;
X
X		*p = '\0';
X	}
X}
X
X#ifdef USE_PORT_CODE
Xxerror (fmt, a1, a2)
Xchar *fmt;
Xchar *a1, *a2;
X{
X	char	buf[BUFSIZ];
X
X	sprintf (buf, fmt, a1, a2);
X	printf ("\nnbatcher: %s\n", fmt);
X	exit (99);
X}
X
X#else
Xxerror (fmt, argp)
Xchar *fmt;
Xint argp;
X{
X	char	buf[BUFSIZ];
X	char	fbuf[BUFSIZ];
X	FILE	prwbuf;
X	register char	*cp;
X	
X	prwbuf._flag = _IOWRT;
X	prwbuf._file = _NFILE;
X	prwbuf._cnt = 32767;
X	prwbuf._ptr = (unsigned char *)buf;
X	prwbuf._base = (unsigned char *)buf;
X	sprintf (fbuf, "\n%s: %s", "nbatcher", fmt);
X	_doprnt (fbuf, (char *)&argp, &prwbuf);
X	putc ('\0', &prwbuf);
X	for (cp = buf; *cp != '\0'; cp++)
X		putchar (*cp);
X
X	exit (99);
X}
X#endif	/* USE_PORT_CODE */
X
Xclear_entry (s)
Xchar *s;
X{
X	register int i;
X
X	for (i=0; i<sizeof(struct file_entry); *s++ = '\0', i++)
X				;
X
X}
SHAR_EOF
echo x - extracting checkwork.c
sed 's/^X//' << 'SHAR_EOF' > checkwork.c
X/************************************************************
X *
X *	checkwork.c - look to see if there's any work
X *		      to do for a site.
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ************************************************************/
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/utsname.h>
X#include <sys/dir.h>
X#include <ctype.h>
X#include <time.h>
X#include "nbatcher.h"
X
Xint
Xwork_to_do ()
X{
X	register char	*p;
X	struct tm	*localtime(), *tp;
X	struct stat	st;
X	char	buf[BUFSIZ];
X	long	time(), clock;
X	int	hour;
X	short	num, upper, lower;
X
X	sprintf (buf, "%s/%s", BATCHDIR, ep.site);
X
X	if (stat(buf, &st) < 0)
X		xerror ("bad stat on %s\n", buf);
X
X	/****************************************
X	 *
X	 * if the size of the batch file is
X	 * zero, return FALSE
X	 *
X	 ***************************************/
X
X	if (st.st_size == 0)
X		return (FALSE);
X
X	/* now see if it time to do anything */
X
X	clock = time ((long *)0);
X	tp = localtime (&clock);
X	hour = tp->tm_hour;
X
X	p = (char *) ep.hour;
X
X	if (*p == '*')		/* match any hour */
X		return (check_uucp());
X
X	if (strncmp(p, "off", 3) == 0)	/* just what it says, off */
X		return (FALSE);
X
X	/***************************************
X	 *
X	 * parse thru hour field to see if
X	 * this is the hour to do work 
X	 *
X	 **************************************/
X
X	num = 0;
X	do {
X		num = num*10 + (*p - '0');
X	} while (isdigit(*++p));
X	if (num == hour)
X		return (check_uucp());
X
X	if (*p == '-') {
X		lower = num;
X		p++;
X		num = 0;
X		do {
X			num = num*10 + (*p - '0');
X		} while (isdigit(*++p));
X		upper = num;
X
X		if (lower < upper) {	/* normal hour range */
X			if (hour >= lower && hour <= upper)
X				return (check_uucp());
X		} else if (lower > upper) {	/* 24 hr. cycle thru */
X			if (hour >= lower || hour <= upper)
X				return (check_uucp());
X		} else
X			return (FALSE);
X	}
X
X	if (*p == ',') {
X		p++;
X		while (*p) {
X			num = 0;
X			do {
X				num = num*10 + (*p - '0');
X			} while (isdigit(*++p));
X			if (num == hour)
X				return (check_uucp());
X			p++;
X		}
X	}
X
X	return (FALSE);
X}
X
X/*	If check_uucp cannot find the remote site
X *	directory, just bypass the byte counting
X *	routine.  This is necessary because the
X *	uucpcleanup daemon, on some sites, removes
X *	the site directory when there's nothing there.
X */
X
Xcheck_uucp()
X{
X	struct utsname	utsn;
X	struct direct	dp;
X	struct stat	st;
X	FILE	*dfp;
X	char	u_name[9], buf[80];
X	short	prefix_len;
X
X	if (uname(&utsn) < 0)
X		xerror ("can't get local nodename\n");
X
X	sprintf (buf, "%s/%s", UUCPDIR, ep.site);
X	if (chdir(buf) < 0) {
X	   fprintf (stderr, "\nnbatcher: can't chdir to %s - bypassing UUCP check\n", buf);
X	   return (TRUE);
X	}
X
X	if ((dfp=fopen(".", "r")) == NULL) {
X	   fprintf (stderr, "\nnbatcher: fopen error on %s - bypassing UUCP check\n", UUCPDIR);
X	   return (TRUE);
X	}
X
X	sprintf (buf, "D.%s", utsn.nodename);
X	prefix_len = (short) strlen(buf);
X	n_bytes = 0;
X	while ((fread((char *)&dp, sizeof(dp), 1, dfp)) == 1) {
X		if (dp.d_ino == 0 || dp.d_name[0] == '.')
X			continue;
X		if (strncmp(dp.d_name, buf, prefix_len))
X			continue;
X		if (stat(dp.d_name, &st) < 0) {
X		  fprintf (stderr, "\nnbatcher: bad stat on UUCP_file %s - bypassing\n", dp.d_name);
X		  continue;
X		}
X		n_bytes += st.st_size;
X		if (n_bytes > ep.m_bytes) {
X			fclose (dfp);
X			return (FALSE);
X		}
X	}
X	fclose (dfp);
X	if (chdir(LIBDIR) < 0)
X		xerror ("can't chdir back to %s\n", LIBDIR);
X
X	if (u_opt == TRUE) {
X		if (n_bytes > 0)
X			fprintf (stderr, "%s: %ld bytes currently on-queue\n",
X					ep.site, n_bytes);
X	}
X	return (TRUE);
X}
SHAR_EOF
echo x - extracting nbatcher.c
sed 's/^X//' << 'SHAR_EOF' > nbatcher.c
X/****************************************************
X *
X *	nbatcher.c - where it really happens.
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ***************************************************/
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/signal.h>
X#include <time.h>
X#include "nbatcher.h"
X
X#define READ	0
X
Xbatch_it ()
X{
X	struct stat	st;
X	FILE	*bfp, *afp;
X	char	fbuf[BUFSIZ], lckfile[40];
X	char	tbuf[80], batfile[40];
X	int	count;
X	int	c;
X
X	if (chdir(BATCHDIR) < 0)
X		xerror ("can't chdir to %s\n", BATCHDIR);
X
X	/********************************************
X	 *
X	 * we create a lock file for two purposes,
X	 * first to make sure a previous nbatcher
X	 * didn't blowup and leave the lock file
X	 * laying around, and second to put the
X	 * remaining news article filenames when
X	 * we go over the max UUCP bytes and there's
X	 * still files remaining for batching.
X	 *
X	 ********************************************/
X
X	sprintf (lckfile, ".%s.lock", ep.site);
X	if (!access(lckfile, 0))
X		xerror ("lockfile already exists for %s\n", ep.site);
X
X	if ((lfp=fopen(lckfile, "w")) == NULL)
X		xerror ("can't create lockfile for %s\n", ep.site);
X
X	/***************************************************
X	 *
X	 * now that we've locked ourselves for this site,
X	 * lets carry on.  We use the copy routine so that
X	 * we don't clash with rnews running and appending
X	 * to the batchfile.
X	 *
X	 **************************************************/
X 
X	sprintf (batfile, "%s.batch", ep.site);
X	copy (ep.site);
X	if ((bfp=fopen(batfile, "r")) == NULL)
X		xerror ("can't open %s/%s for reading\n", BATCHDIR, batfile);
X
X	if (tfile == NULL) {
X		tfile = mktemp("/tmp/bnewsXXXXXX");
X		if ((tfp=fopen(tfile, "w")) == NULL)
X			xerror ("can't open %s for writing\n", tfile);
X	}
X
X	count = fcnt = scnt = 0;
X	cu_bytes = 0;
X	while ((fgets(fbuf, BUFSIZ, bfp)) != NULL) {
X		strip_nl (fbuf);		/* remove the newline */
X		if ((afp=fopen(fbuf, "r")) == NULL) {
X		   fprintf (stderr, "\ncan't read %s for %s\n", fbuf, ep.site);
X		   continue;
X		}
X		if (fstat(fileno(afp), &st) < 0)
X			xerror ("fstat failed on %s\n", fbuf);
X
X		cu_bytes += st.st_size;
X
X		/*****************************************
X		 *
X		 * if the max byte count is exceeded,
X		 * save the remaining files for later
X		 *
X		 ****************************************/
X		if ((cu_bytes + n_bytes) > ep.m_bytes) {
X			fprintf (lfp, "%s\n", fbuf); /* put the '\n' back */
X			while ((fgets(fbuf, sizeof(fbuf), bfp)) != NULL)
X				fputs (fbuf, lfp);
X			fclose (bfp);
X			fclose (lfp);
X			fclose (afp);
X			unlink (batfile);
X
X			/********************************************
X			 *
X			 * now put back the remainder of articles
X			 * to the original batch file
X			 *
X			 *******************************************/
X
X			put_back(lckfile, ep.site);
X			unlink (lckfile);
X			chown (batfile, NEWSUID, NEWSGID);
X			if (count)
X				spoolit ();
X			if (cu_bytes - st.st_size)
X				log_it (cu_bytes - st.st_size);
X
X			return;
X		}
X		sprintf (tbuf, "#! rnews %ld\n", st.st_size);
X		fputs (tbuf, tfp);
X		while ((c=getc(afp)) != EOF)
X			putc (c, tfp);
X		fclose (afp);
X
X		if (++count == nfiles) {
X			spoolit ();
X			count = 0;
X		}
X		fcnt++;
X	}
X
X	/***********************************************
X	 *
X	 * The final spool if lest than nfiles
X	 * is encountered.  Then zero out the
X	 * batchfile and unlink the lock file 
X	 *
X	 **********************************************/
X
X	if (count)
X		spoolit ();
X
X	fclose (bfp);
X	fclose (lfp);
X	unlink (batfile);
X	unlink (lckfile);
X
X	/******************************************
X	 *
X	 * here we log what we've done, and
X	 * if vflg is set, a copy to stdout
X	 * as well 
X	 *
X	 *****************************************/
X
X	log_it (0);
X	if (chdir(LIBDIR) < 0)
X		xerror ("can't chdir back to %s\n", LIBDIR);
X
X}
X
Xspoolit ()
X{
X	struct stat	st;
X	char	cmd[BUFSIZ], cfile[80];
X	FILE	*pfp;
X	int	c;
X
X	fclose (tfp);
X
X	stat (tfile, &st);
X
X	/**************************************
X	 *
X	 * if for some reason the temp file
X	 * is zero, just return 
X	 *
X	 *************************************/
X
X	if (st.st_size == 0)
X		return;
X
X	/**************************************************
X	 *
X	 * if ep.c_bits is set use COMPRESS to compress
X	 * the temp file first
X	 *
X	 *************************************************/
X
X	if (ep.c_bits) {
X		sprintf (cmd, "%s -b%d %s", COMPRESS, ep.c_bits, tfile);
X		if (system(cmd) != 0)
X			xerror ("system(%s) failed\n", cmd);
X
X		strcpy (cfile, tfile);
X		strcat (cfile, ".Z");
X
X		/*****************************************
X		 * if encode is set, skip the fopen.
X		 ****************************************/
X		if (!encode)
X			if ((tfp=fopen(cfile, "r")) == NULL)
X				xerror ("can't open %s for reading\n", cfile);
X
X		/**********************************************
X		 *
X		 * if ep.command has a specific command
X		 * for UUCP spooling, use it.  If not,
X		 * use UUX.
X		 *
X		 *********************************************/
X
X		if (ep.command[0] != '\0')
X			strcpy (cmd, ep.command);
X		else
X			sprintf (cmd, "%s %s!rnews", UUX, ep.site);
X
X		/* now popen the command for writing */
X
X
X		if ((pfp=popen(cmd, "w")) == NULL)
X			xerror ("popen failed on %s\n", cmd);
X
X		/********************************************
X		 * for version 2.10.3 and above,
X		 * prepend `#! cunbatch' or `#! c7unbatch'
X		 * if encode is set.
X		 *
X		 * NOTE: The remote site MUST be able to
X		 *       except this format, or it will
X		 *       be lost!!!
X		 *******************************************/
X
X		if (encode == TRUE) {
X			fputs ("#! c7unbatch\n", pfp);
X			fflush (pfp);
X			encode_it (pfp, cfile);
X		} else {
X			fputs ("#! cunbatch\n", pfp);
X			while ((c=getc(tfp)) !=  EOF)
X				putc (c, pfp);
X		}
X
X		pclose (pfp);
X		if (!encode)
X			fclose (tfp);
X		unlink (cfile);
X	} else {			/* regular batching here */
X		/* skip fopen if encode is set */
X
X		if (!encode)
X			if ((tfp=fopen(tfile, "r")) == NULL)
X				xerror ("can't open %s for reading\n", tfile);
X
X		/*******************************************
X		 *
X		 * if ep.command has a specific command
X		 * for UUCP spooling, use it.  If not,
X		 * use UUX.
X		 *
X		 ******************************************/
X
X		if (ep.command[0] != '\0')
X			strcpy (cmd, ep.command);
X		else
X			sprintf (cmd, "%s %s!rnews", UUX, ep.site);
X
X		if ((pfp=popen(cmd, "w")) == NULL)
X			xerror ("popen failed on %s\n", cmd);
X
X		/***********************************************
X		 * if encode is set, its OK to use it here,
X		 * but we don't send anything to the popen'ed
X		 * command like cnbatch or alike.
X		 **********************************************/
X
X		if (encode == TRUE)
X			encode_it (pfp, tfile);
X		else {
X			while ((c=getc(tfp)) != EOF)
X				putc (c, pfp);
X		}
X
X		pclose (pfp);
X		if (!encode)
X			fclose (tfp);
X	}
X	if ((tfp=fopen(tfile, "w")) == NULL)
X		xerror ("can't re-open %s\n", tfile);
X
X	scnt++;
X}
X
X/*	Encode a file, sending its output to
X *	the popen'ed command.  Creates a
X *	two-way pipe(s) between the command
X *	and the encode program.
X *
X */
X
Xencode_it (p_fp, file)
XFILE	*p_fp;
Xchar	*file;
X{
X	int	pin[2], pout[2];
X	int	count, p_is_open;
X	char	r_buf[BUFSIZ];
X	char	w_buf[BUFSIZ*2];
X	int	f_fd, p_fd;
X	int	ch_pid;
X
X	if (pipe(pin) < 0 || pipe(pout) < 0)
X		xerror ("pipe creation failure\n");
X
X	if ((ch_pid = fork()) == 0) {
X		close (1);		/* close stdout in child */
X		dup (pout[1]);		/* dup stdout in child */
X		close (0);		/* close stdin in child */
X		dup (pin[0]);		/* dup stdin in child */
X
X		/* close un-used file descriptors */
X		close (pin[0]), close (pin[1]);
X		close (pout[0]), close (pout[1]);
X		execl (ENCODE, ENCODE, 0);
X		exit (100);
X	}
X	close (pin[0]), close (pout[1]);	/* un-used in parent */
X
X	/* now open the file for reading */
X
X	if ((f_fd = open(file, READ)) < 0)
X		xerror ("open failure on %s in encode\n", file);
X
X	p_fd = fileno(p_fp);
X	p_is_open = TRUE;
X	while (p_is_open) {
X		if ((count = read(f_fd, r_buf, BUFSIZ)) == -1)
X			xerror ("read error on %s in encode\n", file);
X
X		if (count > 0)
X			if ((write(pin[1], r_buf, count)) != count)
X				xerror ("pipe write error in encode\n");
X
X		/*********************************************
X		 * we must close the write side of the pipe
X		 * when there is less than BUFSIZ read, so
X		 * that the read on the pipe will return 0
X		 ********************************************/
X
X		if (count != BUFSIZ) {
X			close (pin[1]);
X			p_is_open = FALSE;
X		}
X
X		/**********************************************
X		 * now read from the pipe and send its output
X		 * to the popen'ed command
X		 *********************************************/
X
X		if ((count = read(pout[0], w_buf, BUFSIZ*2)) < 0)
X			xerror ("read error on pipe\n");
X		(void) write(p_fd, w_buf, count);
X	}
X	(void) kill(ch_pid, SIGTERM);
X	(void) close(f_fd);
X}
X
Xstrip_nl (s)
Xchar *s;
X{
X
X	while (*s && *s != '\n')
X		s++;
X
X	*s = '\0';
X}
X
SHAR_EOF
echo x - extracting logger.c
sed 's/^X//' << 'SHAR_EOF' > logger.c
X/***************************************************
X *
X *	logger.c - log info about nbatcher
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X **************************************************/
X
X#include <stdio.h>
X#include <time.h>
X#include "nbatcher.h"
X
Xlog_it (bytes)
Xlong	bytes;
X{
X	struct tm	*localtime(), *tp;
X	long	time(), clock;
X	char	logfile[80], buf[BUFSIZ];
X	char	pbuf[BUFSIZ];
X
X	sprintf (logfile, "%s/%s", LIBDIR, "nbatcher.log");
X	if (log == NULL) {
X		if ((log=fopen(logfile, "a")) == NULL)
X		   fprintf (stderr, "\ncan't append to logfile\n");
X	}
X
X	if (log != NULL)
X		rewind (log, 0L, 2);	/* just incase */
X
X	clock = time ((long *)0);
X	tp = localtime (&clock);
X	sprintf (buf, "%.2d/%.2d %.2d:%.2d %s: %d %s batched, %d %s queued\n",
X	   tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, ep.site,
X	   fcnt, (fcnt==1 ? "file" : "files"), scnt,
X	   (scnt==1 ? "file" : "files"));
X
X	if (bytes)
X	   sprintf (pbuf, "%s\tmax bytes reached.  UUCP bytes was %ld, byte count = %ld\n",
X			buf, n_bytes, bytes);
X	else
X		sprintf (pbuf, "%s", buf);
X
X	if (vflg)
X		fprintf (stdout, "%s",pbuf);
X
X	if (log != NULL)
X		fputs (pbuf, log);
X
X}
SHAR_EOF
echo x - extracting nbatcher.h
sed 's/^X//' << 'SHAR_EOF' > nbatcher.h
X/******************************************************
X *	nbatcher.h - defines for nbatcher source
X *
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X *****************************************************/
X
X/* things you might want to change */
X
X#define UUCPDIR		"/usr/spool/uucp"	/* uucp spool directory */
X#define COMPRESS	"/misc/lib/usenet/compress -F -q"   /* where compress
X                                                               resides     */
X#define UUX		"/usr/bin/uux - -r"	/* default command */
X#define DFL_BYTES	100000L			/* default max. UUCP bytes */
X#define NEWSUID		47			/* USENET used id */
X#define NEWSGID		80			/* USENET group id */
X#define ENCODE		"/misc/lib/usenet/encode"  /* where encode resides */
X
X/* things you shouldn't change */
X
X#define FALSE		0
X#define TRUE		1
X#define COLON		':'
X
Xstruct file_entry {		/* structure of control file */
X	char	site[30];	/* name of remote site */
X	char	hour[80];	/* string for hour to batch */
X	short	c_bits;		/* # of compression bits */
X	long	m_bytes;	/* max. # of bytes on UUCP queue */
X	char	command[128];	/* command string */
X} ep;
X
Xextern FILE	*lfp,			/* lockfile pointer */
X		*tfp,			/* tempfile pointer */
X		*log;			/* logfile pointer */
X
Xextern long	n_bytes;		/* # of bytes already on UUCP queue */
Xextern long	cu_bytes;		/* cumculative bytes of batch files */
X
Xchar	*mktemp(),
X	*strcpy(),
X	*strcat();
X
Xextern char	*tfile;			/* temp file */
X
Xextern short	vflg,			/* verbose flag */
X		encode,			/* encode flag */
X		c_opt,			/* check control file option */
X		u_opt,			/* check uucp bytes on-queue */
X		nfiles;			/* number of news articles per
X					   UUCP batch file */
X
Xint	system();
X
Xextern int	fcnt,			/* number of files batched */
X		scnt;			/* spool count */
SHAR_EOF
echo x - extracting bst.c
sed 's/^X//' << 'SHAR_EOF' > bst.c
X/****************************************************************
X *	bst.c - a utility for indicating how many
X *		news articles are ready for batching
X *		for each site in the BACTHDIR directory.
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ****************************************************************/
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X
X
Xchar buf[512];
XFILE *dd;
Xstruct direct dp;
Xstruct stat st;
X
Xmain()
X{
X	int fd, j;
X	int bcnt, lcnt;
X
X	if(chdir(BATCHDIR) != 0) {
X		perror(BATCHDIR);
X		exit(1);
X	}
X
X	if((dd=fopen(".", "r")) == NULL) {
X		printf("can't open %s\n", BATCHDIR);
X		exit(1);
X	}
X
X	while((fread((char *)&dp, sizeof(dp), 1, dd)) == 1) {
X		if(dp.d_ino == 0 || dp.d_name[0] == '.')
X			continue;
X		if(stat(dp.d_name, &st) != 0) {
X			printf("can't stat %s\n", dp.d_name);
X			exit(1);
X		}
X		if(st.st_size <= 0 )
X			continue;
X		if((fd=open(dp.d_name, 0)) < 0) {
X			printf("can't open %s\n", dp.d_name);
X			exit(1);
X		}
X		lcnt = 0;
X		while((bcnt=read(fd,buf,512)) > 0) {
X			for(j=0; j<=bcnt; j++)
X				if(buf[j] == '\n')
X					lcnt += 1;
X		}
X		close(fd);
X		printf("%s\t  %d article", dp.d_name, lcnt);
X		printf("%c\n", lcnt > 1 ? 's' : ' ');
X	}
X}
SHAR_EOF
echo x - extracting copy.c
sed 's/^X//' << 'SHAR_EOF' > copy.c
X/****************************************************
X *
X *	copy.c - make a temp file for nbatcher to
X *		 use instead of the batch file.
X *
X *	R.J. Esposito
X *	Bell of Penna.
X *	June 1986
X *
X ***************************************************/
X
X#include <stdio.h>
X#include <fcntl.h>
X#include "nbatcher.h"
X
X
Xcopy (site)
Xchar	*site;
X{
X	int	bfd, tfd;
X	char	batfile[40], buf[BUFSIZ];
X	int	n;
X
X
X	/********************************************
X	 *
X	 * first lets see if there's a `site.batch' 
X	 * file laying around.
X	 *
X	 *******************************************/
X
X	sprintf (batfile, "%s.batch", site);
X	if (!access(batfile, 0)) {
X		if ((tfd=open(batfile, (O_WRONLY|O_APPEND))) < 0)
X		   xerror ("can't append to %s\n", batfile);
X	} else if((tfd=creat(batfile, 0644)) < 0)
X		xerror ("can't create %s\n", batfile);
X
X	/************************************************************
X	 *
X	 * OK, now to get the articles from the batch file and
X	 * put them in the `site.batch' file.  I use the system
X	 * read/write calls since they are faster than fgets/fputs.
X	 *
X	 ************************************************************/
X
X	if ((bfd=open(site, O_RDONLY)) < 0)
X		xerror ("can't open %s for reading\n", site);
X
X	while ((n=read(bfd, buf, BUFSIZ)) > 0)
X		if ((write(tfd, buf, n)) != n) {
X			close (bfd);
X			close (tfd);
X			unlink (batfile);
X			xerror ("bad write to %s\n", batfile);
X		}
X
X	/* now let's close things up a zero out the batch file */
X
X	close (bfd);
X	close (open(site, O_TRUNC));
X	close (tfd);
X}
X
X/*****************************************************
X *
X *	put_back - If the max. number of uucp_bytes
X *		   was reached, we copy the lckfile
X *		   back to the original batch file.
X *
X *****************************************************/
X
Xput_back (lfile, site)
Xchar	*lfile, *site;
X{
X	int	lfd, sfd;
X	char	buf[BUFSIZ];
X	int	n;
X
X	if ((lfd=open(lfile, O_RDONLY)) < 0)
X		xerror ("can't re-open lockfile %s\n", lfile);
X
X	if ((sfd=open(site, (O_RDWR|O_APPEND))) < 0)
X		xerror ("can't re-open site file %s\n", site);
X
X	while ((n=read(lfd, buf, BUFSIZ)) > 0)
X		if ((write(sfd, buf, n)) != n)
X			xerror ("write error in put_back() for %s\n", ep.site);
X
X	close (lfd);
X	close (sfd);
X}
SHAR_EOF
echo x - extracting README
sed 's/^X//' << 'SHAR_EOF' > README
XThe program provides a better way to handling batching of USENET news
Xto neighboring sites.  IT works with 2.10B and later, including
X2.10.3B, which provides compression batching.
X
XPlease send any questions and bug reports to me (bpa!espo) for
Xdistribution to the net.  This software is public domain, and cannot be
Xsold for any profit.
X
XNbatcher was written for UNIX (trademark of AT&T) System V, but should
Xbe compatible with most version currently being used today.  It is
Xpresently running on a VAX (trademark of DEC) 11/780 feeding 5 remote
Xsites with news.  Two important issues MUST be noted here.
X
XSince nbatcher checks the UUCP directory for each site listed in the
Xcontrol file, directory configuration should be UUCPDIR/sitename, e.g.
X/usr/spool/uucp/foo for site foo.  But since not everyone is generic,
XI've allowed nbatcher to skip over the UUCP byte counting routine if
Xthis is not so.  In addition to this, some uucpcleanup daemons remove
Xthe site directory if there's nothing spooled there.  When this
Xhappens, nbatcher will notify you of this and continue on.
X
XThe other issue is that you MUST configure your NEWSLIB/sys file using the
Xbatch syntax as described in the USENET Installation document.  Field 3
Xshould contain the `F' flag indicating a batched transmission for that
Xsite and field 4 should contain the filename where news article's full
Xpathname will be appended to.  Nbatcher requires that the filename in
Xfield 4 match the sitename in the control file.  For example, the entry:
X	foo:net,mod,usa,na,to.foo:F:/usr/spool/batchnews/foo
Xsays that all news articles going to site `foo' will be batched, using
Xthe file `foo' in /usr/spool/batchnews.  Nbatcher's control file entry:
X	foo:3,14,22:16:150000:
Xwill get the news article's full pathname from /usr/spool/batchnews/foo
Xfor batching.  This requirement MUST be adhered to for nbatcher to work
Xcorrectly.  I believe most sites that batch news use this type of
Xconfiguration, since its easy to keep track of a remote site's work.
XNote that the name of the directory "/usr/spool/batchnews" is set
Xin the Makefile.
X
XThe manual page describes the control file, as does nbatcher.doc.  Note
Xthat even though nbatcher indicates max bytes has been reached, if that
Xsite gets compressed batches, the next time nbatcher runs for that site
Xwork could get scheduled.
X
XAlso included is a utility program called "bst," BatchSTatus, which
Xshows who many news articles are currently in the batch file for each
Xsystem.  Just type "make bst" for that and copy it to where you want,
Xusually LIBDIR.  Note that bst does reads on the directory structure
Xdirectly.
X
XInstallation is simple:  look at the first few #define's in nbatcher.h;
Xalso edit the Makefile for the appropriate BATCHDIR and LIBDIR for your
Xsite.  Type "make" or "make install."
X
XNOTE:  Read the comments in parse.c concerning MAX_BYTES.  This is the
Xmaximum amount of bytes per site that's allowed in the control file for
Xm_bytes.  Change it at your own risk!
X
X    Bob Esposito   bpa!espo
X    Bell of Penna.
SHAR_EOF
echo x - extracting nbatcher.ctl
sed 's/^X//' << 'SHAR_EOF' > nbatcher.ctl
X*
X*	NBATCHER.CTL
X*	Edit and install in your NEWSLIB directory.
X*
X*  Comments start with *; data lines look like:
X*	site:hours:bits:queue_size:command
X*  Where
X*	site		= name of the remote site
X*	hour		= when to do work (* is all, off is never, 22-4 is ok)
X*	bits		= passed on to compress via -b; null gets default
X*	queue_size	= Max # bytes allowed in UUCP queue before postponing
X*	command		= optional command line
X*
X*  See manpage and README for more info.
SHAR_EOF
echo x - extracting nbatcher.doc
sed 's/^X//' << 'SHAR_EOF' > nbatcher.doc
X
XDETAILED DESCRIPTION OF nbatcher.ctl FILE
X
X			   Document for nbatcher
X
XAs distributed, the nbatcher.ctl contains a terse summary of its
Xformat.  For each site you feed news to, providing that site uses the
X":F:BATCHDIR/site" batching syntax in the sys file, a corresponding
Xentry should exist in nbatcher.ctl  Each line is a five-field,
Xcolon-separated entry indicating what to do for that site.
X
XThe format is:
X	site:hour:c_bits:m_bytes:command
XThe site field is the name of the UUCP site that will get the batched
Xnews.  There MUST be a file in the BATCHDIR (in my case
X/usr/spool/batchnews) matching this name that contains a listing of
Xfiles to batch.  This is where rnews puts the news article filenames
Xwhen batching is used.  Nbatcher will complain about mismatches.
X
XNext is the hour field.  The syntax directly imitates the crontab entry
Xfor hour.  If hour = "*", than assume a match for every hour.  If hour
X= "off", then no work will ever be spooled for this site.  (This is the
Xonly difference from crontab).  You can specify a specific hour, like 8
Xor 09 or 22.  Or a range of hours, like 10-15, meaning check for work
Xfrom 10 AM thru 3 PM.  (you can also cycle thru a 24 hr. period by
Xsaying 22-4, which will assume a match for 10PM thru 4AM).  Also, hours
Xcomma separated like 7,14,21, says check for work at 7 AM, 4PM and 9PM
Xonly.  This gives lots of flexibility for sites that feed multiple
Xremotes.
X
XNext is the c_bits field, which has a value of 9-16, or may be left
Xblank.  If non-null, this field is directly passed on to compress with
Xthe -b flag.  If it is null, then no compression is done for this
Xsite.  This field can also be used to execute the encode program, which
Xproduces 7-bit ascii output for those sites that require it for transmission
Xto other sites.  If the letter `e' appears in this field, nbatcher will call
Xencode for you.  An example, :16e: will first compress the batched news,
Xand then encode it for the remote site.  If you require just encoding without
Xcompression, that's also possible, but caution should be used here.  The
Xcommand to execute on the remote will have to be something than rnews.
X
XThe m_bytes field is the maximum number of bytes allowed on the UUCP
Xqueue at any time.  It defaults to 100K bytes, and if its greater than
X1MB (see parse.c about MAX_BYTES), than it uses 1MB as the default
Xnumber of bytes.
X
XWhat nbatcher does is first check the UUCPDIR for that site and sums up
Xthe number of bytes already on-queue.  The size of each news article is
Xaccumulated prior to batching to the tempfile, and this accumulation +
Xthe UUCP on-queue bytes are checked to see if they surpass the m_bytes
Xvalue.  If not, things proceed normally.  But if it would exceed the
Xmax value, then spool whats already been batched, and save the
Xremainder of the articles for the next scheduled batch.
X
XThis is so noted in the nbatcher.log file, showing the UUCP bytes that
Xwere on-queue (if any), and the number of bytes that were spooled.
XAlso, if nbatcher is run from a terminal instead of from cron, a copy
Xof what gets logged is sent to the terminal.
X
XThe last field is the command field.  This optional field is used for
Xspecific UUCP command execution.  As written, it defaults to "uux - -r
Xsite!rnews".  Since my uux doesn't require the -z option for rnews, I
Xleft it out of the UUX define in nbatcher.h  This can easily be changed
Xfor your own taste, or just use the command field.
X
XExample:
X	site-A:3,10:16:450000:
XCheck for work for site-A at 3AM and 10AM.  Use compress with
X16 bits and only spool up to 450K bytes, using "uux - -r site-A!rnews".
X
XAnother example:
X	foo:23-4::300000:uux - -r -z foo!rnews
XCheck for work for foo between 11PM and 4AM.  No compression is used
Xand spool up to 300K bytes using the command field.
X
XThis format should help ease UUCP congestion on the local site for
Xnews.  I currently feed 5 remote sites, compressed and non-compressed
Xformats, and have noticed a vast improvement in disk space as well as
Xout port availability for UUCP.  It has allowed me to check for work
Xfor all sites every hour, since there's a limit on how much gets
Xqueued.  And if the remote doesn't answer on the hourly UUCP poll, so
Xwhat!!  Nbatcher just won't spool any more until the m_bytes limit
Xon-queue is reduced.
X
XThe best results is to run nbatcher, via cron as the USENET
Xadministrator (NEWSUID), at least once per hour.
X
XNbatcher was designed to weed out any illegal syntax in the control
Xfile, but its not bug-proof, so use some discretion or use the -c
Xoption to check your entries.
X
XBob Esposito
XBell of Penna.
XJune 1986
SHAR_EOF
echo x - extracting nbatcher.1
sed 's/^X//' << 'SHAR_EOF' > nbatcher.1
X.TH NBATCHER 1 LOCAL
X.SH NAME
Xnbatcher  \- new batching system for news
X.SH SYNOPSIS
X.B nbatcher \[-c\] \[-u\]
X.IR "(run out of " cron ".)"
X.SH DESCRIPTION
X.PP
X.I Nbatcher
Xis a program designed to send batched USENET data out
Xin an orderly and controlled fashion, while providing alternative
Xtransmission methods.  As such, it is a replacement for
X.I csendbatch
Xand the like, which typically require many entries in
X.IR crontab .
X.I Nbatcher
Xis intended to be run hourly out of
X.I cron
Xas the USENET administrator (NEWSID).
X.PP
X.I Nbatcher
Xscans the file
X.I nbatcher.ctl
Xin the NEWSLIB directory to determine if work should be spooled
Xfor a system.  In the control file, lines starting with asterisks
Xare ignored; data lines are comprised of five colon\-separated
Xfields:
X.RS
Xsite:hour:bits:queue_size:command
X.RE
X.TP
XThe ``site'' field is the USENET neighbor; it is the same as the site in the
Xnews
X.I sys
Xfile.
X.TP
XThe ``hours'' field is patterned after
X.IR cron's.
XIf the hour is ``off,'' no work is spooled for the site.  An hour of
X``*'' matches every hour.  It is also possible to specify specific
Xhours, (e.g., 8, 09, or 22), a comma-separated list (e.g., 8,09,22),
Xor a twenty\-four range, like 10-\15 for 10am through 3pm and 22\-4,
Xfor 10pm through 4am.
X.TP
XThe ``bits'' field specifies the number of bits to use in compression;
Xit should be a number between nine and 16, inclusive, or null.  If
Xa number is specified, it is passed on to the
X.I compress
Xprogram via the ``\-b'' flag.  Using the 7-bit encodeing scheme can
Xalso be accomplished by including the letter `e' in this field.
X.TP
XThe ``queue_size'' field specifies the maximum number of bytes allowed
Xin the UUCP queue for this site.  The default is 100K.
XThe UUCP queue size is determined by lookin in the
X.IR /usr/spool/uucp/ site
Xdirectory; if it doesn't exist, the check is bypassed.  If there is
Xdata in the UUCP queue,
X.I nbatcher
Xwill only queue up as many USENET transfers as will fit within the limit
Xspecified by the ``queue_size'' field.
X.TP
XThe ``command'' field is used to specific the UUCP command that should
Xbe used to queue the job; the default is ``uux \- \-r site!rnews'';
Xnote the absence of the ``\-z'' flag.
X.PP
XThe -c option is used to check the validity/syntax of the control
Xfile.  It will not batch/spool any news.
X.PP
XThe -u option will report the current number of bytes spooled
Xfor each site that has work spooled.  It will not batch/spool
Xany news.
X.PP
XTo set up a USENET neighbor to be controlled by nbatcher, the news
X.I sys
Xentry for the neighbor must be modified to specify the ``F'' flag,
Xand the file used to contain the article names must be
X.RI BATCHDIR/sysname ,
Xwhere BATCHDIR is set in the Makefile as distributed.
X.SH "FILES"
XNEWSLIB/nbatcher.log
X.br
X.in +1i
Xa logfile of failures, postponements, etc.
X.in -1i
X.SH BUGS
XParsing of the control file is fairly robust, but not perfect.
SHAR_EOF
echo x - extracting Makefile
sed 's/^X//' << 'SHAR_EOF' > Makefile
X#
X#	Makefile for nbatcher Version 1.4
X#
X#	R.J. Esposito
X#	Bell of Penna.
X#	June 1986
X#
X
X#	You MUST define BATCHDIR as the place where USENET
X#	puts the articles to be batched.
X#
X#	LIBDIR is where you USENET library is and also
X#	MUST be defined.
X#
X#	Define PCODE for parse.c if you don't have/can't use
X#	_doprnt.
X
XBATCHDIR = /usr/spool/batchnews
XLIBDIR = /misc/lib/usenet
X
XCFLAGS = -O -c
XLFLAGS = -s
XDFLAGS = -DBATCHDIR='"$(BATCHDIR)"' -DLIBDIR='"$(LIBDIR)"'
X# PCODE = -DUSE_PORT_CODE
X
XOBJS = define.o main.o parse.o checkwork.o nbatcher.o logger.o copy.o
X
XSRC = define.c main.c parse.c checkwork.c nbatcher.c logger.c nbatcher.h bst.c \
X copy.c
XDOCS = README nbatcher.ctl nbatcher.doc nbatcher.1
X
Xall: $(OBJS)
X	$(CC) $(DFLAGS) $(LFLAGS) -o nbatcher $(OBJS)
X	chmod 0755 nbatcher
X
Xinstall: all
X	cp nbatcher $(LIBDIR)
X	chmod 0755 $(LIBDIR)/nbatcher
X	cp nbatcher.ctl $(LIBDIR)/nbatcher.ctl
X	chmod 0644 $(LIBDIR)/nbatcher.ctl
X
Xdefine.o: nbatcher.h define.c
X	$(CC) $(CFLAGS) $(DFLAGS) define.c
X
Xmain.o: nbatcher.h main.c
X	$(CC) $(CFLAGS) $(DFLAGS) main.c
X
Xcopy.o: nbatcher.h copy.c
X	$(CC) $(CFLAGS) $(DFLAGS) copy.c
X
Xparse.o: nbatcher.h parse.c
X	$(CC) $(CFLAGS) $(DFLAGS) $(PCODE) parse.c
X
Xcheckwork.o: nbatcher.h checkwork.c
X	$(CC) $(CFLAGS) $(DFLAGS) checkwork.c
X
Xnbatcher.o: nbatcher.h nbatcher.c
X	$(CC) $(CFLAGS) $(DFLAGS) nbatcher.c
X
Xlogger.o: nbatcher.h logger.c
X	$(CC) $(CFLAGS) $(DFLAGS) logger.c
X
Xbst: bst.c
X	$(CC) $(CFLAGS) $(DFLAGS) bst.c
X	$(CC) $(LFLAGS) -o bst bst.o
X
Xclean:
X	rm -f *.o *.shar
X
Xclobber: clean
X	rm -f nbatcher
X
X
Xshar:
X	shar -v $(SRC) $(DOCS) Makefile > nbatcher.shar
SHAR_EOF
exit
-- 
        //////////////////////////////////////////
        * Bob Esposito     espo@bpa.bell-atl.com *
        //////////////////////////////////////////