/* :ts=4
 * news sys file reading functions
 *
 *	$Log$
 */

#include <errno.h>
#include "fixerrno.h"
#include <stdio.h>
#include <ctype.h>
#ifdef unix
# include <sys/types.h>
# include <sys/stat.h>
#endif /* unix */
#include "libc.h"
#include "fgetmfs.h"
#include "news.h"
#include "config.h"
#include "system.h"

#define BTCHDIR "out.going/"		/* prefix of relative batch file name */
#define BTCHPFX BTCHDIR				/* prefix of default batch file name */
#define BTCHSFX "/togo"				/* suffix of same */
#define CMDPFX "uux - -r -z "		/* prefix of default command */
#define CMDSFX "!rnews"				/* suffix of same */

/* private */
static FILE *fp = NULL;				/* stream for ctlfile(filerelname) */
static char filerelname[] = "sys";	/* filename relative to $NEWSCTL */

/* forward decls */
FORWARD char *parsecolon(), *reparse();
FORWARD void readsys(), parsesysln(), parse(), parseflags(), newartfile();
FORWARD int spacein();

/* exports */
struct system *firstsys = NULL;	/* cache: 1st sys of in-core sys file */
struct system *currsys = NULL;	/* current system */

/* imports */
extern boolean donesys();
extern struct system *mysysincache();
extern void rewsys(), remmysys(), advcurrsys(), setupsys();

struct system *
oursys()			/* return our sys entry */
{
	register struct system *sys = mysysincache();
	static struct system fakesys;

	if (sys == NULL) {
		rewsys(fp);
		while ((sys = nextsys()) != NULL && !STREQ(sys->sy_name, hostname()))
			;
		if (sys == NULL) {
			/* no entry: cook one up; no need to malloc members */
			fakesys.sy_name = hostname();
			fakesys.sy_excl = NULL;
			fakesys.sy_ngs = "all";
			fakesys.sy_distr = "all";
			fakesys.sy_flags = 0;
			fakesys.sy_lochops = 0;
			fakesys.sy_cmd = "";
			fakesys.sy_next = NULL;
			sys = &fakesys;
		}
		remmysys(sys);			/* for future reference */
	}
	return sys;
}

/*
 * Return the next sys entry, which may span multiple lines.
 * Returned pointer points at a struct whose lifetime (and that of its
 * members) is not promised to last beyond the next call to nextsys();
 * copy it and its pointed-to strings if you need them for longer.
 *
 * Note that readsys() reads one entry on small systems, but the entire
 * sys file on big systems, so the common code in this file is subtle.
 */
struct system *
nextsys()
{
	struct system *retsys;

	if (firstsys == NULL && fp == NULL)
		if ((fp = fopenwclex(ctlfile(filerelname), "r")) == NULL)
			return NULL;
	if (fp != NULL && firstsys == NULL)
		readsys();
	retsys = currsys;
	advcurrsys();
	return retsys;
}

/*
 * On small systems, read one entry; else read whole sys file (done once only).
 * Ignores '#' comments and blank lines; uses cfgetms to read possibly-
 * continued lines of arbitrary length.
 */
STATIC void
readsys()
{
	register char *sysline;

	setupsys(fp);		/* reuse currsys or rewind sys file */
	while ((sysline = cfgetms(fp)) != NULL) {
		if (sysline[0] != '#' && sysline[0] != '\n')
			parsesysln(sysline);
		free(sysline);
		if (donesys())		/* early exit if on disk (small) */
			return;
	}
	(void) nfclose(fp);
	fp = NULL;
	rewsys(fp);
}

static char *curr, *next;			/* parsing state */

/*
 * Parse (and modify) sysline into *currsys, which is malloced here
 * and freed iff on a small system, in readsys(), see freecursys().
 *
 * Side-effect: sysline has a trailing newline removed.
 */
STATIC void
parsesysln(sysline)
register char *sysline;
{
	register struct system *sysp =(struct system *)nemalloc(sizeof *sysp);
	char *flagstring;

	trim(sysline);
	next = sysline;
	parse(&sysp->sy_name);
	parse(&sysp->sy_ngs);
	parse(&flagstring);
	parse(&sysp->sy_cmd);
	errno = 0;
	if (spacein(sysp->sy_name))
		errunlock(
		"whitespace in system name (or exclusions) of sys entry for `%s'",
			sysp->sy_name);
	if (spacein(sysp->sy_ngs))
		errunlock(
		"whitespace in newsgroups (or distributions) of sys entry for `%s'",
			sysp->sy_name);
	if (spacein(flagstring))
		errunlock("whitespace in flags of sys entry for `%s'",
			sysp->sy_name);
	/* could check for extra fields here */

	parseflags(flagstring, sysp);
	free(flagstring);		/* malloced by parse */
	sysp->sy_next = NULL;

	/* reparse for embedded slashes */
	sysp->sy_excl = reparse(sysp->sy_name, '/');
	sysp->sy_distr = reparse(sysp->sy_ngs, '/');
	if (sysp->sy_distr == NULL)	/* default distr is ngs... */
		sysp->sy_distr = sysp->sy_ngs;

	sysdeflt(sysp);			/* fill in any defaults */

	/* stash *sysp away on the tail of the current list of systems */
	*(firstsys == NULL? &firstsys: &currsys->sy_next) = sysp;
	currsys = sysp;
}

STATIC int
spacein(s)
register char *s;
{
	return strchr(s, ' ') != NULL || strchr(s, '\t') != NULL;
}

/*
 * fill in defaults in sysp.
 *
 * expand a name of "ME" to hostname().
 * If an empty batch file name was given, supply a default
 * ($NEWSARTS/BTCHPFX system BTCHSFX).
 * Prepend $NEWSARTS/BTCHDIR to relative file names.
 * If an empty command was given, supply a default (uux - -r -z system!rnews).
 * (This *is* yucky and uucp-version-dependent.)
 */
void
sysdeflt(sysp)
register struct system *sysp;
{
	if (STREQ(sysp->sy_name, "ME")) {
		free(sysp->sy_name);	/* malloced by parse */
		sysp->sy_name = strsave(hostname());
	}
	if (sysp->sy_flags&FLG_BATCH && sysp->sy_cmd[0] == '\0') {
		register char *deffile = nemalloc((unsigned) strlen(BTCHPFX) +
			strlen(sysp->sy_name) + strlen(BTCHSFX) + SIZENUL);

		(void) strcpy(deffile, BTCHPFX);
		(void) strcat(deffile, sysp->sy_name);
		(void) strcat(deffile, BTCHSFX);
		/* frees old sysp->sy_cmd, deffile */
		newartfile(sysp, deffile);
	}
#ifdef unix
	else if (sysp->sy_flags&FLG_BATCH && sysp->sy_cmd[0] != FNDELIM)
#elif defined(AMIGA)
	else if (sysp->sy_flags&FLG_BATCH && !strchr(sysp->sy_cmd, ':'))
#endif
	{
		register char *absfile = nemalloc((unsigned) strlen(BTCHDIR) +
			strlen(sysp->sy_cmd) + SIZENUL);

		(void) strcpy(absfile, BTCHDIR);
		(void) strcat(absfile, sysp->sy_cmd);
		/* frees old sysp->sy_cmd, absfile */
		newartfile(sysp, absfile);
	} else if (!(sysp->sy_flags&FLG_BATCH) && sysp->sy_cmd[0] == '\0') {
		free(sysp->sy_cmd);	/* malloced by parse */
		sysp->sy_cmd = nemalloc((unsigned) STRLEN(CMDPFX) +
			strlen(sysp->sy_name) + STRLEN(CMDSFX) + SIZENUL);
		(void) strcpy(sysp->sy_cmd, CMDPFX);
		(void) strcat(sysp->sy_cmd, sysp->sy_name);
		(void) strcat(sysp->sy_cmd, CMDSFX);
	}
}

STATIC void
newartfile(sysp, file)		/* replace sysp->sy_cmd with artfile(file) */
register struct system *sysp;
register char *file;
{
	free(sysp->sy_cmd);		/* malloced by parse */
	sysp->sy_cmd = strsave(artfile(file));
	free(file);
}

/*
 * Parse "next" to colon into malloced storage, return its ptr via "into".
 * *into is freed iff on a small system, in readsys(), see freecursys().
 */
STATIC void
parse(into)
register char **into;
{
	curr = next;
	if (curr == NULL)
		*into = strsave("");
	else {
		next = parsecolon(curr);
		*into = strsave(curr);
	}
}

STATIC char *
parsecolon(line)		/* return NULL or ptr. to byte after colon */
char *line;
{
	register char *colon;

	STRCHR(line, ':', colon);
	if (colon != NULL)
		*colon++ = '\0';
	return colon;
}

/*
 * replace "delim" in "field" with a NUL and return the address of the byte
 * after the NUL (the address of the second subfield), or NULL if no
 * "delim" was present.
 */
STATIC char *
reparse(field, delim)
char *field;
int delim;
{
	register char *delimp = strchr(field, delim);

	if (delimp != NULL)
		*delimp++ = '\0';
	return delimp;
}

/*
 * Parse sys file flags into sysp.
 */
STATIC void
parseflags(flags, sysp)
register char *flags;
register struct system *sysp;
{
	sysp->sy_flags = 0;
	sysp->sy_lochops = 0;		/* default L value */
	errno = 0;
	for (; *flags != '\0'; flags++)
		switch (*flags) {
		case 'A':
			errunlock("A news format not supported", "");
			/* NOTREACHED */
		case 'B':		/* mostly harmless */
			break;
		case 'f':
			sysp->sy_flags |= FLG_BATCH|FLG_SZBATCH;
			break;
		case 'F':
			sysp->sy_flags |= FLG_BATCH;
			break;
		case 'I':		/* NNTP hook: write msgids, !files */
			sysp->sy_flags |= FLG_BATCH|FLG_IHAVE;
			break;
		case 'L':		/* Ln */
			sysp->sy_flags |= FLG_LOCAL;
			sysp->sy_lochops = 0;
			while (isascii(flags[1]) && isdigit(flags[1])) {
				sysp->sy_lochops *= 10;
				sysp->sy_lochops += *++flags - '0';
			}
			break;
		case 'm':		/* send only moderated groups */
			sysp->sy_flags |= FLG_MOD;
			break;
		case 'N':
			errunlock(
	"The N flag is a wasteful old kludge; see the I flag instead.", "");
			/* NOTREACHED */
		case 'n':		/* NNTP hook: write files+msgids */
			sysp->sy_flags |= FLG_BATCH|FLG_NBATCH;
			break;
		case 'u':		/* send only unmoderated groups */
			sysp->sy_flags |= FLG_UNMOD;
			break;
		case 'U':		/* mostly harmless */
			break;
		case 'H':		/* bugger off */
		case 'S':		/* bugger off */
		case 'M':		/* multicast: obs., see batcher */
		case 'O':		/* multicast: obs., see batcher */
		default:
			errunlock("unknown sys flag `%s' given", flags);
			/* NOTREACHED */
		}
}

void
rewndsys()
{
	rewsys(fp);
}
