/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * ZMailer transport scheduler.
 */

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <sys/param.h>
#include "scheduler.h"
#include <sys/stat.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <mail.h>
#include NDIR_H

#ifndef	_IOLBF
#define	_IOLBF	0200
#endif	/* !_IOLBF */

#ifdef	HONEYBUM/* not really SVID, just this stupid honeywell compiler */
#define	MAX_ENTRIES 3000
#else	/* sane pcc */
#define MAX_ENTRIES 10000
#endif	/* honeywell pcc */

struct sptree *spt_mesh[SIZE_L];

char	*progname;
char	*postoffice;
char	*rendezvous;
char	*pidfile = PID_SCHEDULER;
char	*mailshare, *mailbin;
char	*log;
int	mustexit;
int	gotalarm;
int	dumpq;
int	canexit;
int	rereadcf;
int	verbose;
int	querysocket = -1;	/* fd of TCP socket to listen for queries */
int	subsubdirs;	/* use subdirectories under each channel directory */
#define	MEM_MALLOC 1000
int	stickymem = MEM_MALLOC;	/* for strnsave() */
int	D_alloc = 0;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	struct ctlfile *cfp;
	char *config, *cp;
	int i, daemonflg, c, errflg, version, fd;
	long now, offout, offerr;
	struct config_entry *tp;
	extern struct config_entry *readconfig(), *rereadconfig();
	extern struct ctlfile *schedule();
	extern int numkids, optind, sweepinterval;
	extern char *qlogdir, *qcf_suffix, *qoutputfile;
	extern char *optarg, *Version;
	extern char *vtx_to_dir();
	extern u_char *rfc822date();
	extern time_t time();
	extern time_t sweepretry;
	extern void syncweb(), detach(), killprevious(), doagenda();
	extern void mux(), ipcpause(), die(), prversion();
	extern void queryipcinit(), qprint();
	extern SIGNAL_TYPE sig_exit(), sig_alarm(), sig_iot(), sig_readcf();
	extern int loginit(), eopen(), eunlink();

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		++progname;
	postoffice = rendezvous = log = config = NULL;
	daemonflg = 1;
	verbose = subsubdirs = errflg = version = 0;
	while ((c = getopt(argc, argv, "disvf:L:P:Q:V")) != EOF)
		switch (c) {
		case 'f':	/* override default config file */
			config = optarg;
			break;
		case 'L':	/* override default log file */
			log = optarg;
			break;
		case 'P':	/* override default postoffice */
			postoffice = optarg;
			break;
		case 'Q':	/* override default mail queue rendezvous */
			rendezvous = optarg;
			break;
		case 'v':	/* be verbose and synchronous */
			verbose = 1;
			daemonflg = 0;
			break;
		case 'd':	/* daemon again */
			daemonflg = 1;
			break;
		case 'i':	/* interactive */
			daemonflg = 0;
			break;
		case 's':
			subsubdirs = 1;
			break;
		case 'V':
			version = 1;
			daemonflg = 0;
			break;
		case '?':
		default:
			errflg++;
			break;
		}
	if (errflg) {
		(void) fprintf(stderr,
			"Usage: %s [-disvV -f configfile -L logfile -P postoffice -Q rendezvous]\n",
			progname);
		exit(128+errflg);
	}
	if ((mailshare = getzenv("MAILSHARE")) == NULL)
		mailshare = MAILSHARE;
	if ((mailbin = getzenv("MAILBIN")) == NULL)
		mailbin = MAILBIN;
	if ((cp = getzenv("LOGDIR")) != NULL)
		qlogdir = cp;
	if (daemonflg && log == NULL) {
		log = emalloc(2 + (u_int)(strlen(qlogdir) + strlen(progname)));
		(void) sprintf(log, "%s/%s", qlogdir, progname);
	}
	if (log != NULL) {
		/* loginit is a signal handler, so can't pass log */
		if (loginit() < 0)	/* do setlinebuf() there */
			die(1, "log initialization failure");
		/* close and reopen log files */
		(void) signal(SIGHUP, (SIGNAL_TYPE (*)())loginit);
	} else {
		(void) signal(SIGHUP, SIG_IGN); /* no surprises please */
		(void) setvbuf(stdout, (char *)NULL, _IOLBF, 0);
		(void) setvbuf(stderr, (char *)NULL, _IOLBF, 0);
	}
#ifdef	SIGUSR1
	(void) signal(SIGUSR1, sig_readcf);
#endif	/* SIGUSR1 */
	if (verbose || version) {
		prversion("scheduler");
		if (version)
			exit(0);
		(void) putc('\n', stderr);
	}
	offout = ftell(stdout);
	offerr = ftell(stderr);
	if (config == NULL) {
		config = emalloc(3 + (u_int)(strlen(mailshare)
						   + strlen(progname)
						   + strlen(qcf_suffix)));
		sprintf(config, "%s/%s.%s", mailshare, progname, qcf_suffix);
	}
	if ((tp = readconfig(config)) == NULL) {
		cp = emalloc(strlen(config)+50);
		(void) sprintf(cp, "null control file: %s", config);
		die(1, cp);
		/* NOTREACHED */
	}

	if (postoffice == NULL && (postoffice = getzenv("POSTOFFICE")) == NULL)
		postoffice = POSTOFFICE;
	if (chdir(postoffice) < 0 || chdir(SCHEDULERDIR) < 0)
		(void) fprintf(stderr, "%s: cannot chdir to %s/%s.\n",
				progname, postoffice, SCHEDULERDIR);
	if (rendezvous == NULL && (rendezvous = getzenv("RENDEZVOUS")) == NULL)
		rendezvous = qoutputfile;
	if (daemonflg) {
		/* X: check if another daemon is running already */
		if (!verbose
		    && (offout < ftell(stdout) || offerr < ftell(stderr))) {
			fprintf(stderr, "%d %d %d %d\n", offout, ftell(stdout), offerr, ftell(stderr));
			fprintf(stderr, "%s: daemon not started.\n", progname);
			die(1, "too many scheduler daemons");
			/* NOTREACHED */
		}
		detach();		/* leave worldy matters behind */
		now = time((long *)0);
		(void) printf("%s: scheduler daemon (%s)\n\tstarted at %s\n",
				progname, Version, (char *)rfc822date(&now));
		killprevious(SIGTERM, pidfile);
	}
	for (i = 0; i < SIZE_L; ++i)
		spt_mesh[i] = sp_init();
#ifdef  LOG_PID
#ifdef  LOG_MAIL
	openlog("scheduler", LOG_PID, LOG_MAIL);
#else   /* !LOG_MAIL */
	openlog("scheduler", LOG_PID);
#endif  /* LOG_MAIL */
#endif  /* LOG_PID */
	if (optind < argc) {
		/* process the specified control files only */
		for (; optind < argc; ++optind) {
			if ((fd = eopen(argv[optind], O_RDWR, 0)) < 0)
				continue;
			/* the close(fd) is done in vtxprep */
			cfp = schedule(fd, argv[optind], tp);
			if (cfp == NULL && verbose)
				fprintf(stderr, "Nothing scheduled for %s!\n",
						argv[optind]);
			(void) eunlink(argv[optind]);
		}
		doagenda();
		exit(0);
	}
	mustexit = gotalarm = dumpq = rereadcf = 0;
	canexit = 0;
	(void) signal(SIGALRM, sig_alarm);	/* process agenda */
	(void) signal(SIGPIPE, SIG_IGN);
	(void) signal(SIGUSR2, sig_iot);	/* dump queue info */
	cp = emalloc(strlen(TRANSPORTDIR)+4);
	(void) sprintf(cp, "../%s", TRANSPORTDIR);
	syncweb(cp, 0, tp);
	(void) free(cp);
	canexit = 1;
	(void) signal(SIGTERM, sig_exit);	/* split */
	sweepretry = 1;
	queryipcinit();
	(void) alarm(sweepinterval);
#ifdef	MALLOC_TRACE
	mal_leaktrace(1);
#endif	/* MALLOC_TRACE */
	while (!mustexit) {
		canexit = 0;
		syncweb(".", 1, tp);
		doagenda();
		/*
		 * Due to a possible race condition, gotalarm = 0 must
		 * have been done inside doagenda() instead of here.
		 */
		/* mux on the stdout of children */
		while (!gotalarm && numkids > 0) {
			if (dumpq) {
				qprint(-1);
				dumpq = 0;
			}
			if (rereadcf) {
				tp = rereadconfig(tp, config);
				rereadcf = 0;
			}
			if (numkids > 0)
				mux();
		}
		canexit = 1; /* may be possible to move this after doagenda() */
		/* wait for an alarm call */
		while (!gotalarm) {
			if (dumpq) {
				qprint(-1);
				dumpq = 0;
			}
			if (rereadcf) {
				tp = rereadconfig(tp, config);
				rereadcf = 0;
			}
			ipcpause();
		}
	}
#ifdef	MALLOC_TRACE
	mal_dumpleaktrace(stderr);
#endif	/* MALLOC_TRACE */
	if (mustexit)
		die(0, "signal");
	exit(0);
	/* NOTREACHED */
	return 0;
}

SIGNAL_TYPE
sig_exit()
{
	extern void die();
	
	if (querysocket >= 0) {		/* give up mailq socket asap */
		(void) close(querysocket);
		querysocket = -1;
	}
	if (canexit)
		die(0, "signal");
	mustexit = 1;
}

SIGNAL_TYPE
sig_alarm()
{
	gotalarm = 1;
	(void) signal(SIGALRM, sig_alarm);
}

SIGNAL_TYPE
sig_iot()
{
	dumpq = 1;
	(void) signal(SIGUSR2, sig_iot);
}

SIGNAL_TYPE
sig_readcf()
{
	rereadcf = 1;
	(void) signal(SIGUSR1, sig_readcf);
}

/*
 * Absorb any new files that showed up in our directory.
 */

void
syncweb(dir, nukefile, cehead)
	char *dir;
	int nukefile;
	struct config_entry *cehead;
{
	struct stat stbuf;
	int fd;
	char file[MAXNAMLEN+1];
	DIR *dirp;
	struct NDIR_TYPE *dp;
	static time_t modtime = 0;
	extern struct ctlfile *schedule();
	extern int estat(), eopen(), eunlink();

	if (estat(dir, &stbuf) < 0)
		return;
	if (stbuf.st_mtime == modtime)
		return;
	modtime = stbuf.st_mtime;
	dirp = opendir(dir);
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
		if (mustexit)
			break;
		if (isascii(dp->d_name[0]) && isdigit(dp->d_name[0])) {
			if (strcmp(dir, ".") == 0)
				strcpy(file, dp->d_name);
			else
				(void) sprintf(file, "%s/%s", dir, dp->d_name);
			if ((fd = eopen(file, O_RDWR, 0)) < 0) {
				if (getuid() == 0)
					(void) eunlink(file); /* hrmpf! */
			} else {
				(void) schedule(fd, file, cehead);
				if (nukefile)
					(void) unlink(file);
				else	/* we're scanning TRANSPORTDIR */
					/* unlink it from the SCHEDULERDIR */
					(void) unlink(dp->d_name);
					/*
					 * This avoids a race when the
					 * scheduler starts up while the router
					 * is pushing stuff at it.  Note that
					 * there is a window of vulnerability
					 * if the router links to transport
					 * then scheduler instead of vice
					 * versa.
					 */
			}
		}
	}
	(void) closedir(dirp);
}

/*
 * The schedule() function is in charge of reading the control file from
 * the scheduler directory, and creating all the appropriate links to the
 * control file.
 * Since it is convenient to do so here, it also goes through the list
 * of transport directives and schedules the appropriate things to be
 * done at the appropriate time in the future.
 */
struct ctlfile *
schedule(fd, file, cehead)
	int fd;
	char *file;
	struct config_entry *cehead;
{
	struct ctlfile *cfp;
	char path[MAXPATHLEN], buf[MAXPATHLEN], *dir;
	struct vertex *vp;
	extern struct ctlfile *slurp(), *vtxprep();
	extern char *vtx_to_dir();
	extern void mklink(), unctlfile(), vtxdo();
	extern int subsubdirs;
	extern int eunlink();

	/* read and process the control file */
	if ((cfp = vtxprep(slurp(fd))) == NULL) {
		(void) eunlink(file);	/* everything here has been processed */
		return NULL;
	}
	if (cfp->head == NULL) {
		unctlfile(cfp);
		return NULL;
	}
	if (cfp->contents != NULL && cfp->vfp == NULL) {
		free(cfp->contents);
		cfp->contents = NULL;
	}
	for (vp = cfp->head; vp != NULL; vp = vp->next[L_CTLFILE]) {
		if (subsubdirs) {	/* channel/host/file */
			dir = vtx_to_dir(vp, buf);
		} else if (vp->next[L_CTLFILE] == NULL
			   || vp->orig[L_CHANNEL] !=
			              vp->next[L_CTLFILE]->orig[L_CHANNEL]) {
			dir = vp->orig[L_CHANNEL]->name;   /* channel/file */
		} else
			dir = NULL;
		if (dir != NULL) {
			(void) sprintf(path, "%s/%lu", dir, cfp->id);
			mklink(file, path);
			if (cfp->vfp != NULL)
				(void) fprintf(cfp->vfp, "linked to %s\n", path);
			if (verbose)
				(void) printf("linked %s,%d to %s\n",
					       vp->cfp->mid, vp->cfp->id, path);
		}

		vtxdo(vp, cehead);
	}
	return cfp;
}

struct ctlfile *
slurp(fd)
	int fd;
{
	register char *s;
	register int i;
	char *contents;
	long offset[MAX_ENTRIES], *ip, *lp;
	struct stat stbuf;
	struct ctlfile *cfp;
	extern int efstat(), eread();

	if (fd < 0)
		return NULL;
	if (efstat(fd, &stbuf) < 0) {
		(void) close(fd);
		return NULL;
	}
	if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
		(void) close(fd);
		return NULL;	/* XX: give error */
	}
	contents = emalloc((u_int) stbuf.st_size);
	if (eread(fd, contents, stbuf.st_size) != stbuf.st_size) { /* slurp */
		(void) close(fd);
		return NULL;
	}
	/* go through the file and mark it off */
	i = 0;
	offset[i++] = 0L;
	for (s = contents; s - contents < stbuf.st_size; ++s) {
		if (*s == '\n') {
			*s++ = '\0';
			if (s - contents < stbuf.st_size) {
				offset[i++] = s - contents;
				if (*s == _CF_MSGHEADERS) {
					/* find a \n\n combination */
					while (!(*s == '\n' && *(s+1) == '\n'))
						if (s-contents < stbuf.st_size)
							++s;
						else
							break;
					if (s - contents >= stbuf.st_size) {
						/* XX: header ran off file */
					}
				}
			}
		}
	}
	cfp = (struct ctlfile *)emalloc((u_int) (sizeof (struct ctlfile) + i * (sizeof offset[0])));
	/* closing fd must be done in vtxprep(), so we can unlock stuff easy */
	cfp->fd = fd;
	cfp->vfp = NULL;
	cfp->uid = stbuf.st_uid;
	cfp->ctime = 0;
	cfp->haderror = 0;
	cfp->head = NULL;
	cfp->nlines = i;
	cfp->contents = contents;
	cfp->logident = NULL;
	cfp->erroraddr = NULL;
	cfp->id = stbuf.st_ino;
	cfp->mid = NULL;
	cfp->mark = V_NONE;
	lp = &(cfp->offset[0]);
	ip = &(offset[0]);
	while (--i >= 0)
		*lp++ = *ip++;
	return cfp;
}

struct offsort {
	long	offset;
	int	myidx;
};

/* Used by the qsort comparison routine, bcp points to the control file bytes */
static char *bcp;

int
bcfcn(a, b)
	struct offsort *a, *b;
{
	extern int cistrcmp();
	
	return cistrcmp(bcp + a->offset, bcp + b->offset);
}

struct ctlfile *
vtxprep(cfp)
	struct ctlfile *cfp;
{
	register int i, n;
	register long *lp;
	register struct offsort *op;
	int svn;
	char *cp, *channel, *host, *l_channel, *l_host;
	char *echannel, *ehost, *l_echannel, *l_ehost, mfpath[100], flagstr[2];
	struct spblk *spl;
	struct vertex *vp, *pvp, **pvpp, *head;
	struct stat stbuf;
	struct offsort offarr[MAX_ENTRIES];
	extern void link_in(), deletemsg(), zeroconfigentry();
	extern int lockaddr(), estat(), cistrcmp();

	if (cfp == NULL)
		return NULL;
	sp_install((u_int)cfp->id, (u_char *)cfp, 0, spt_mesh[L_CTLFILE]);
	channel = host = NULL;
	/* copy offsets into local array */
	op = &offarr[0];
	lp = &cfp->offset[0];
	for (i = 0; i < cfp->nlines; ++i, ++lp) {
		cp = cfp->contents + *lp + 1;
		if (*cp == _CFTAG_LOCK) {
			/*
			 * This is assumed to happen when we move old control
			 * files into the scheduler directory. Since this is
			 * the only processing that needs to be done except
			 * for the rename(), we might as well do it here,
			 * instead of in some separate program.
			 *
			 * NB! If transport agents are running, this may
			 * cause duplicate delivery of a message.  Better than
			 * no delivery...
			 */
			*cp = _CFTAG_NORMAL;	/* unlock it */
			(void) lockaddr(cfp->fd, (long) (cp - cfp->contents),
						 _CFTAG_LOCK, _CFTAG_NORMAL);
		}
		if (*cp == _CFTAG_NORMAL) {
			switch (*--cp) {
			case _CF_RECIPIENT:
				op->offset = *lp + 2;
				op->myidx = i;
				++op;
				break;
			case _CF_DIAGNOSTIC:
				cfp->haderror = 1;
				break;
			case _CF_MESSAGEID:
				cfp->mid = emalloc((u_int)strlen(cp+2)+1);
				(void) strcpy(cfp->mid, cp+2);
				break;
			case _CF_LOGIDENT:
				cfp->logident = emalloc((u_int)strlen(cp+2)+1);
				(void) strcpy(cfp->logident, cp+2);
				break;
			case _CF_ERRORADDR:
				cfp->erroraddr = emalloc((u_int)strlen(cp+2)+1);
				(void) strcpy(cfp->erroraddr, cp+2);
				break;
			case _CF_OBSOLETES:
				deletemsg(cp+2, cfp);
				break;
			case _CF_VERBOSE:
#ifdef	USE_SETREUID
				(void) setreuid(0, cfp->uid);
#else	/* SVID */
#ifdef	USE_SETEUID
				(void) seteuid(cfp->uid);
#else	/* ! USE_SETEUID */
				/* Only reversible under recent SVID! */
				(void) setuid(cfp->uid);
#endif	/* USE_SETEUID */
#endif	/* USE_SETREUID */
				cfp->vfp = fopen(cp+2, "a");
#ifdef	USE_SETREUID
				(void) setreuid(0, 0);
#else	/* SVID */
#ifdef	USE_SETEUID
				(void) seteuid(0);
#else	/* ! USE_SETEUID */
				/* Only works under recent SVID! */
				(void) setuid(0);
#endif	/* USE_SETEUID */
#endif	/* USE_SETREUID */
				if (cfp->vfp != NULL) {
					(void) fseek(cfp->vfp, (long)0, 2);
					(void) setvbuf(cfp->vfp, (char *)NULL,
							  _IOLBF, 0);
				}
				if (cfp->vfp != NULL && cfp->mid != NULL)
					(void) fprintf(cfp->vfp,
						"scheduler processing %s\n",
						cfp->mid);
				break;
			}
		}
	}
	(void) close(cfp->fd);	/* closes the fd opened in schedule() */
	if (cfp->mid != NULL)
		(void) sprintf(mfpath, "../%s/%s", QUEUEDIR, cfp->mid);
	if (cfp->mid == NULL || cfp->logident == NULL
	    || estat(mfpath, &stbuf) < 0) {
		spl = sp_lookup((u_int)cfp->id, spt_mesh[L_CTLFILE]);
		if (spl != NULL)
			sp_delete(spl, spt_mesh[L_CTLFILE]);
		if (cfp->mid)
			(void) free(cfp->mid);
		if (cfp->logident)
			(void) free(cfp->logident);
		if (cfp->vfp != NULL) {
			fprintf(cfp->vfp,
				"aborted due to missing information\n");
			(void) fclose(cfp->vfp);
		}
		(void) free(cfp->contents);
		(void) free((char *)cfp);
		return NULL;
	}
	cfp->ctime = stbuf.st_ctime;
	cfp->fd = -1;
	n = op - &offarr[0];
	/* sort it to get channels and channel/hosts together */
	bcp = cfp->contents;
	qsort((char *)offarr, n, sizeof (struct offsort), bcfcn);
	/*
	 * Loop through them; whenever either channel or host changes,
	 * make a new vertex. All such vertices must be linked together.
	 */
	flagstr[0] = ' ';
	flagstr[1] = '\0';
	l_channel = l_echannel = l_host = l_ehost = flagstr;
	svn = 0;
	pvp = NULL;
	head = NULL;
	pvpp = &head;
	for (i = 0; i < n; ++i) {
		channel = bcp + offarr[i].offset /* - 2 */;
		if ((echannel = strchr(channel, ' ')) == NULL)	/* error! */
			continue;
		*echannel = '\0';
		host = echannel + 1;
		while (isascii(*host) && isspace(*host))
			++host;
		if ((ehost = strchr(host, ' ')) == NULL)	/* error! */
			continue;
		*ehost = '\0';
		/* compare with the last ones */
		if (cistrcmp(channel, l_channel) || cistrcmp(host, l_host)) {
			/* wrap and tie the old vertex node */
			if (i != svn) {
				vp = (struct vertex *)emalloc((u_int) (sizeof (struct vertex) + (i - svn - 1) * sizeof (int)));
				vp->cfp = cfp;
				zeroconfigentry(&vp->ce);
				vp->next[L_CTLFILE] = NULL;
				vp->prev[L_CTLFILE] = pvp;
				vp->message = NULL;
				vp->retryindex = 0;
				vp->nextitem = NULL;
				vp->previtem = NULL;
				vp->wakeup = 0;
				vp->proc = NULL;
				vp->ngroup = i - svn;
				vp->attempts = 0;
				while (svn < i) {
					vp->index[i-svn-1] = offarr[svn].myidx;
					++svn;
				}
				*pvpp = vp;
				pvpp = &vp->next[L_CTLFILE];
				pvp = vp;
				link_in(L_HOST, vp, (u_char *)l_host);
				link_in(L_CHANNEL, vp, (u_char *)l_channel);
			}
			/* create a new vertex node */
			svn = i;
		}
		/* stick the current 'r'eceived line into the current vertex */
		/* restore the characters */
		*l_echannel = *l_ehost = ' ';
		l_echannel = echannel;
		l_ehost = ehost;
		l_channel = channel;
		l_host = host;
	}
	/* wrap and tie the old vertex node (this is a copy of code above) */
	if (i != svn) {
		vp = (struct vertex *)emalloc((u_int) (sizeof (struct vertex) + (i - svn - 1) * sizeof (int)));
		vp->cfp = cfp;
		zeroconfigentry(&vp->ce);
		vp->next[L_CTLFILE] = NULL;
		vp->prev[L_CTLFILE] = pvp;
		vp->message = NULL;
		vp->retryindex = 0;
		vp->nextitem = NULL;
		vp->previtem = NULL;
		vp->proc = NULL;
		vp->wakeup = 0;
		vp->ngroup = i - svn;
		vp->attempts = 0;
		while (svn < i) {
			vp->index[i-svn-1] = offarr[svn].myidx;
			++svn;
		}
		*pvpp = vp;
		pvpp = &vp->next[L_CTLFILE];
		pvp = vp;
		link_in(L_HOST, vp, (u_char *)host);
		link_in(L_CHANNEL, vp, (u_char *)channel);
	}
	*l_echannel = *l_ehost = ' ';
	/*
	for (vp = head; vp != NULL; vp = vp->next[L_CTLFILE]) {
		printf("--\n");
		for (i = 0; i < vp->ngroup; ++i)
			printf("%s\n", cfp->contents+cfp->offset[vp->index[i]]);
	}
	*/
	cfp->head = head;
	return cfp;
}

int
vtxfill(vp, tp)
	struct vertex *vp;
	struct config_entry *tp;
{
	extern int match();
	
	/* if the channel doesn't match, there's no hope! */
	if (verbose)
		(void) printf("ch? %s %s\n",
			      vp->orig[L_CHANNEL]->name, tp->channel);
	if (!match(tp->channel, vp->orig[L_CHANNEL]->name))
		return 0;
	if (verbose)
		printf("host %s %s\n", vp->orig[L_HOST]->name, tp->host);
	if (!(tp->host == NULL || tp->host[0] == '\0'
		       || (tp->host[0] == '*' && tp->host[1] == '\0'))) {
		if (!match(tp->host, vp->orig[L_HOST]->name))
			return 0;
	}
	if (tp->interval != -1) vp->ce.interval = tp->interval;
	if (tp->expiry != -1) vp->ce.expiry = tp->expiry;
	if (tp->expiryform != NULL) vp->ce.expiryform = tp->expiryform;
	if (tp->uid != -1) vp->ce.uid = tp->uid;
	if (tp->gid != -1) vp->ce.gid = tp->gid;
	if (tp->maxkids != -1) vp->ce.maxkids = tp->maxkids;
	if (tp->maxkidChannel != -1) vp->ce.maxkidChannel = tp->maxkidChannel;
	if (tp->maxkidHost != -1) vp->ce.maxkidHost = tp->maxkidHost;
	if (tp->nretries > 0) {
		vp->ce.nretries = tp->nretries;
		vp->ce.retries = tp->retries;
	}
	if (tp->command != NULL) {
		vp->ce.command = tp->command;
		vp->ce.argv = tp->argv;
	}
	if (tp->bychannel != -1)
		vp->ce.bychannel = tp->bychannel;
	if (tp->byhost != -1)
		vp->ce.byhost = tp->byhost;
	vp->ce.flags |= tp->flags;
	vp->ce.mark = vp->ce.pending = 0;
	if (tp->skew > 0) vp->ce.skew = tp->skew;
	return 1;
}

void
vtxdo(vp, cehead)
	struct vertex *vp;
	struct config_entry *cehead;
{
	struct config_entry *tp;
	int n;
	extern char *progname;
	extern void vtxprint(), reschedule();

	/*
	 * go through scheduler control file entries and
	 * fill in the blanks in the vertex specification
	 */
	n = 0;
	for (tp = cehead; tp != NULL; tp = tp->next) {
		n += vtxfill(vp, tp);
		if (vp->ce.command != NULL)
			break;
	}
	if (n == 0) {
		(void) fprintf(stderr,
			"%s: no pattern matched %s/%s address\n",
			progname,
			vp->orig[L_CHANNEL]->name, vp->orig[L_HOST]->name);
		/* XX: memory leak here? */
		return;
	}

	/* set default values */
	if (vp->ce.expiry > 0)
		vp->ce.expiry += vp->cfp->ctime;
	else
		vp->ce.expiry = 0;
	if (vp->ce.interval == -1)
		vp->ce.interval = 3600;
	if (vp->ce.maxkids == -1)
		vp->ce.maxkids = 1000;
	if (vp->ce.maxkidChannel == -1)
		vp->ce.maxkidChannel = 1000;
	if (vp->ce.maxkidHost == -1)
		vp->ce.maxkidHost = 1000;
	if (vp->ce.byhost == -1)
		vp->ce.byhost = 0;
	if (vp->ce.bychannel == -1)
		vp->ce.bychannel = 0;

	if (verbose)
		vtxprint(vp);

	if ((vp->ce.flags | FF_GANGSCHEDULE)
	    && vp->prev[L_HOST] != NULL
	    && vp->prev[L_CHANNEL] != NULL
	    && vp->ce.retries == vp->prev[L_HOST]->ce.retries
	    && vp->orig[L_HOST] == vp->prev[L_HOST]->orig[L_HOST]
	    && vp->orig[L_CHANNEL] == vp->prev[L_CHANNEL]->orig[L_CHANNEL]) {
		vp->retryindex = vp->prev[L_HOST]->retryindex;
		reschedule(vp, -(vp->prev[L_HOST]->wakeup), -1);
	} else
		reschedule(vp, 0, -1);
}

int
match(pattern, string)
	register char	*pattern;
	register char	*string;
{
	while (1) {
		switch (*pattern) {
		case '*':
			pattern++;
			do {
				if (match(pattern, string))
					return 1;
			} while (*string++ != '\0');
			return 0;
		case '[':
			if (*string == '\0')
				return 0;
			if (*(pattern+1) == '^') {
				++pattern;
				while ((*++pattern != ']')
				       && (*pattern != *string))
					if (*pattern == '\0')
						return 0;
				if (*pattern != ']')
					return 0;
				string++;
				break;
			}
			while ((*++pattern != ']') && (*pattern != *string))
				if (*pattern == '\0')
					return 0;
			if (*pattern == ']')
				return 0;
			while (*pattern++ != ']')
				if (*pattern == '\0')
					return 0;
			string++;
			break;
		case '?':
			pattern++;
			if (*string++ == '\0')
				return 0;
			break;
		case '\0':
			return (*string == '\0');
		default:
			if (*pattern++ != *string++)
				return 0;
		}
	}
}

/*
 * This routine links a group of addresses (described by what vp points at)
 * into the Tholian Web (err, our matrix). The flag (either L_HOST or
 * L_CHANNEL) selects a namespace of strings pointed at by s. It just
 * happens we need 2 (host and channel names), and we don't care what
 * they are as long as they are in separate spaces.
 */
void
link_in(flag, vp, s)
	int flag;
	struct vertex *vp;
	u_char *s;
{
	register u_char *cp;
	struct web *wp;
	struct spblk *spl;

	for (cp = s; *cp != '\0'; ++cp)
		if (isascii(*cp) && isupper(*cp))
			*cp = tolower(*cp);
	spl = sp_lookup(symbol(s), spt_mesh[flag]);
	if (spl == NULL || (wp = (struct web *)spl->data) == NULL) {
		wp = (struct web *)emalloc(sizeof (struct web));
		bzero(wp, sizeof (struct web));
		sp_install(symbol(s), (u_char *)wp, 0, spt_mesh[flag]);
		wp->name = strnsave((char *)s, strlen((char *)s));
		wp->kids = 0;
		wp->link = NULL;
		wp->lastlink = NULL;
		wp->fifohead = NULL;
		wp->fifotail = NULL;
	}
	vp->next[flag] = NULL;
	vp->prev[flag] = wp->lastlink;
	vp->orig[flag] = wp;
	if (wp->lastlink != NULL)
		wp->lastlink->next[flag] = vp;
	wp->lastlink = vp;
	if (wp->link == NULL)
		wp->link = vp;
}


/*
 * Generate a file name corresponding to a link to the control file.
 * On busy systems, this should probably map into a channel/host/file
 * pathname. Unfortunately, some OS's cannot handle long host names.
 * If that becomes a problem, it should be possible to select another
 * scheme on a per-channel basis. It should be kept in mind that any
 * scheme which produces an N-to-M reverse mapping (if the host name
 * portion of the pathname is truncated or missing, say), requires
 * some smarts elsewhere to weed out duplicate link pathnames.
 */
char *
vtx_to_dir(vp, buf)
	struct vertex *vp;
	char *buf;
{
	(void) strcpy(buf, vp->orig[L_CHANNEL]->name);
	(void) strcat(buf, "/");
	(void) strcat(buf, vp->orig[L_HOST]->name);
	return buf;
}

struct ctlfile *
file_to_ctl(s)
	char *s;
{
	char *file;
	struct spblk *spl;

	if ((file = strrchr(s, '/')) == NULL)
		file = s;
	else
		++file;
	spl = sp_lookup((u_int)atoi(file), spt_mesh[L_CTLFILE]);
	if (spl == NULL)
		return NULL;
	return (struct ctlfile *)spl->data;
}

void
mklink(from, to)
	char *from, *to;
{
	char *cp;
	extern int errno;
	extern char *progname, *strerror();
	extern int emkdir();

	if (link(from, to) >= 0)
		return;
	switch (errno) {
	case ENOENT:	/* this may mean a subdirectory doesn't exist */
		for (cp = strchr(to, '/'); cp != NULL; cp = strchr(cp+1, '/')) {
			*cp = '\0';
			(void) emkdir(to, 0755);
			*cp = '/';
		}
		if (link(from, to) >= 0)
			return;
		/* fall through (down to default action) */
	case EEXIST:	/* ignore this, since file may exist previously */
		if (errno == EEXIST)
			return;
		/* fall through (for the sake of second attempt in ENOENT) */
	default:	/* serious problem of some kind */
		(void) fprintf(stderr, "%s: link(%s, %s): %s\n", progname,
			from, to, strerror(errno));
		break;
	}
	return;
}
