/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */
/*
 *	Lots of modifications (new guts, more or less..) by
 *	Matti Aarnio <mea@nic.funet.fi>  (copyright) 1992-1995
 */

/*
 * ZMailer transport scheduler.
 */

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

#ifdef HAVE_DIRENT_H
# include <dirent.h>
#else /* not HAVE_DIRENT_H */
# define dirent direct
# ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif /* HAVE_SYS_NDIR_H */
# ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif /* HAVE_SYS_DIR_H */
# ifdef HAVE_NDIR_H
#  include <ndir.h>
# endif /* HAVE_NDIR_H */
#endif /* HAVE_DIRENT_H */

#include "zmsignal.h"
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif

#include "prototypes.h"

#ifndef	MAXNAMLEN /* POSIX.1 ... */
#define MAXNAMLEN NAME_MAX
#endif

#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];

#ifdef	MALLOC_TRACE
struct conshell *envarlist = NULL;
#endif	/* MALLOC_TRACE */

#define TRANSPORTMAXNOFILES 32 /* Number of files a transporter may
				  need open -- or any of its children.. */
int	transportmaxnofiles = TRANSPORTMAXNOFILES; /* Default value */
char	*progname;
extern char *postoffice;	/* At  mail_open()  */
char	*rendezvous;
char	*pidfile = PID_SCHEDULER;
char	*mailshare;
char	*log;
static int mustexit;
static int gotalarm;
static int dumpq;
static int canexit;
static int rereadcf;
static int dlyverbose = 0;
time_t	sched_starttime;
int	verbose = 0;
int	querysocket = -1;	/* fd of TCP socket to listen for queries */
int	D_alloc = 0;
int	hungry_childs = 0;
int	global_wrkcnt = 0;
int	mailq_Q_mode = 0;
int	syncstart = 0;		/* while set, the thread subsystem shall start
				   no childs! */
extern int forkrate_limit;	/* How many forks per second ? */

static int vtxprep_skip      = 0;
static int vtxprep_skip_any  = 0;
static int vtxprep_skip_lock = 0;
static time_t next_dirscan     = 0;
static time_t next_idlecleanup = 0;
static struct sptree *dirscan_mesh = NULL;
static int newents_limit = 200;

#include "memtypes.h"
extern memtypes stickymem;

static struct ctlfile *schedule __((int fd, char *file, struct config_entry *ce, long ino));
static struct ctlfile *vtxprep __((struct ctlfile *));
static int  vtxmatch __((struct vertex *, struct config_entry *));
static void link_in __((int flag, struct vertex *vp, u_char *s));
static int  lockverify __((struct ctlfile *, char *));
static int  globmatch   __((char *, char*));
static void vtxdo   __((struct vertex *, struct config_entry *, char *));

static RETSIGTYPE sig_exit   __((int sig));
static RETSIGTYPE sig_alarm  __((int sig));
static RETSIGTYPE sig_iot    __((int sig));
static RETSIGTYPE sig_readcf __((int sig));

extern char *Version;
extern char *optarg;
extern char *strerror __((int errno));
extern int cistrcmp __((char *s1, char *s2));
extern int efstat __((int fd, struct stat *stbuf));
extern int emkdir __((char *dirpath, int mode));
extern int eopen __((char *filename, int flags, int mode));
extern int eread __((int fd, void *buf, int count));
extern int errno;
extern int estat __((char *filename, struct stat *stbuf));
extern int eunlink __((char *filename));
extern int optind;

static int timeserver_pid = 0;
extern time_t mytime __((time_t *));
static void init_timeserver __((void));

#ifdef HAVE_SELECT /* Well, not exactly kosher assumption.. */
/* extern int gettimeofday __((struct timeval *,)); */
#else
extern time_t time __((time_t *));
#endif
extern u_char *rfc822date __((time_t *));
extern void detach __((void));
extern void die __((int rc, char *reason));
extern int  killprevious __((int signal, char *pidfile));
extern void prversion __((char *name));

struct dirstatname {
	struct stat st;
	long ino;
	char name[1]; /* Allocate enough size */
};
struct dirqueue {
	int	wrkcount;
	int	wrkspace;
	struct dirstatname **stats;
};

static int dirqueuescan __((const char *dir, struct dirqueue *dq));
static int syncweb __((struct dirqueue *dq, struct config_entry *ce));

int global_maxkids = 1000;
time_t now;
FILE *statuslog = NULL;

static void cfp_free __((struct ctlfile *cfp, struct spblk *spl));
static void cfp_free(cfp, spl)
struct ctlfile *cfp;
struct spblk   *spl;
{
	struct vertex *vp, *nvp;
	int resyncing = (spl != NULL);

	/* Delete from the  spt_mesh[]  */

	if (spl == NULL)
	  spl = sp_lookup((u_long)(cfp->id), spt_mesh[L_CTLFILE]);
	if (spl != NULL)
	  sp_delete(spl, spt_mesh[L_CTLFILE]);

	/* And from memory */

	if (cfp->vfp) {
	  fprintf(cfp->vfp,
		  "ordered deletion of task file from scheduler memory (%s)\n",
		  cfp->mid);
	  fclose(cfp->vfp);
	}

	if (cfp->head != NULL) {
	  for (vp = cfp->head; vp != NULL; vp = nvp) {
	    nvp = vp->next[L_CTLFILE];
	    vp->ngroup = 0;
	    unvertex(vp,-1,1); /* Don't unlink()! Just free()! */
	  }
	} else {
	  free_cfp_memory(cfp);
	}
}

static int ctl_free __((struct spblk *spl));
static int ctl_free(spl)
struct spblk *spl;
{
	cfp_free((struct ctlfile *)spl->data, NULL);
	return 0;
}


/*
 *  free_cfp_memory(cfp) -- release all of the memory associated with the
 *  control file -- some of it is conditionally allocated, or perhaps
 *  previously released..  (cfp->contents, for example)
 */
void free_cfp_memory(cfp)
struct ctlfile *cfp;
{
	if (cfp->contents)	free(cfp->contents);
	if (cfp->mid)		free(cfp->mid);
	if (cfp->erroraddr)	free(cfp->erroraddr);
	if (cfp->errorchan)	free(cfp->errorchan);
	if (cfp->logident)	free(cfp->logident);
	if (cfp->envid)		free(cfp->envid);
	free((char *)cfp);
}


int
main(argc, argv)
	int argc;
	char *argv[];
{
	struct ctlfile *cfp;
	char *config, *cp;
	int i, daemonflg, c, errflg, version, fd;
	long offout, offerr;
	struct config_entry *tp;
	struct dirqueue dqb;
	struct dirqueue *dq = &dqb;
	int freeze = 0;

	/* setlinebuf(stderr);  -- no need for this ? */

	mytime(&sched_starttime);

	memset(&dqb,0,sizeof(dqb));
	dirscan_mesh = sp_init();

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		++progname;

	stickymem = MEM_MALLOC;

	resources_maximize_nofiles();

	/* The theory is, that scheduler needs circa 10 fd's for its own uses,
	   and it will use all others on child-process communication fifos. */

	global_maxkids = resources_query_nofiles()-10;

	postoffice = rendezvous = log = config = NULL;
	daemonflg = 1;
	dlyverbose = 0;
	syncstart = 0;
	verbose = errflg = version = 0;
	while ((c = getopt(argc, argv, "divE:f:Fl:L:N:P:q:QR:SVW")) != EOF)
		switch (c) {
		case 'f':	/* override default config file */
			config = optarg;
			break;
		case 'E':
			newents_limit = atoi(optarg);
			if (newents_limit < 10)
			  newents_limit = 10;
			break;
		case 'F':
			freeze = 1;
			break;
		case 'l':
			statuslog = fopen(optarg,"a");
			if (!statuslog) {
			  perror("Can't open statistics log file (-l)");
			  exit(1);
			}
			break;
		case 'L':	/* override default log file */
			log = optarg;
			break;
		case 'N':
			if ((transportmaxnofiles = atoi(optarg)) < 10)
				transportmaxnofiles = TRANSPORTMAXNOFILES;
			break;
		case 'P':	/* override default postoffice */
			postoffice = optarg;
			break;
		case 'Q':
			mailq_Q_mode = 1;
			break;
		case 'q':	/* override default mail queue rendezvous */
			rendezvous = optarg;
			break;
		case 'R':	/* How many new childs per second to be spawned ? */
			forkrate_limit = atoi(optarg);
			if (forkrate_limit < 1)
			  forkrate_limit = 1;
			break;
		case 'v':	/* be verbose and synchronous */
			++verbose;
			daemonflg = 0;
			break;
		case 'W':
			dlyverbose = verbose;
			verbose = 0;
			break;
		case 'd':	/* daemon again */
			daemonflg = 1;
			break;
		case 'i':	/* interactive */
			daemonflg = 0;
			break;
		case 'V':
			version = 1;
			daemonflg = 0;
			break;
		case 'S':
			syncstart = 1;
			break;
		case '?':
		default:
			errflg++;
			break;
		}
	if (errflg) {
	  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 ((cp = getzenv("LOGDIR")) != NULL)
	  qlogdir = cp;
	if (daemonflg && log == NULL) {
	  log = emalloc(2 + (u_int)(strlen(qlogdir) + strlen(progname)));
	  sprintf(log, "%s/%s", qlogdir, progname);
	}
	if (log != NULL) {
	  /* loginit is a signal handler, so can't pass log */
	  if (loginit(SIGHUP) < 0) /* do setlinebuf() there */
	    die(1, "log initialization failure");
	  /* close and reopen log files */
	  SIGNAL_HANDLE(SIGHUP, (RETSIGTYPE(*)())loginit);
	} else {
	  SIGNAL_IGNORE(SIGHUP); /* no surprises please */
	  setvbuf(stdout, (char *)NULL, _IOLBF, 0);
	  setbuf(stderr,NULL);	/* [mea] No buffering on stdout.. */
	}
#ifdef USE_SIGREAPER
# ifdef SIGCLD
	SIGNAL_HANDLE(SIGCLD,  sig_chld);
# else
	SIGNAL_HANDLE(SIGCHLD, sig_chld);
# endif
#else
# ifdef SIGCLD
	SIGNAL_HANDLE(SIGCLD,SIG_IGN);		/* Auto-reap the kids.. */
# else
	SIGNAL_HANDLE(SIGCHLD,SIG_IGN);
# endif
#endif

#ifdef	SIGUSR1
	SIGNAL_HANDLE(SIGUSR1, sig_readcf);
#endif	/* SIGUSR1 */
	if (verbose || version) {
	  prversion("scheduler");
	  if (version)
	    exit(0);
	  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);
	  sprintf(cp, "null control file, propably errors in it: %s", config);
	  die(1, cp);
	  /* NOTREACHED */
	}

	if (postoffice == NULL && (postoffice = getzenv("POSTOFFICE")) == NULL)
	  postoffice = POSTOFFICE;

	if (chdir(postoffice) < 0 || chdir(TRANSPORTDIR) < 0)
	  fprintf(stderr, "%s: cannot chdir to %s/%s.\n",
		  progname, postoffice, TRANSPORTDIR);

	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, "%ld %ld %ld %ld\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 */
	  mytime(&now);
	  printf("%s: scheduler daemon (%s)\n\tstarted at %s\n",
		 progname, Version, (char *)rfc822date(&now));
	}
	/* Actually we want this to act as daemon,
	   even when not in daemon mode.. */
	if (killprevious(SIGTERM, pidfile) != 0) {
	  printf("%s: Can't write my pid to a file ?? Out of diskspace ??\n",progname);
	  die(1,"Can't write scheduler pid to a file!?");
	  /* NOTREACHED */
	}

	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) {
	    long ino = atol(argv[optind]);
	    if ((fd = eopen(argv[optind], O_RDWR, 0)) < 0)
	      continue;
	    /* the close(fd) is done in vtxprep */
	    cfp = schedule(fd, argv[optind], tp, ino);
	    if (cfp == NULL) {
	      if (verbose)
		fprintf(stderr, "Nothing scheduled for %s!\n",
			argv[optind]);
	    } else
	      eunlink(argv[optind]);
	  }
	  doagenda();
	  exit(0);
	}
	mustexit = gotalarm = dumpq = rereadcf = 0;
	canexit = 0;
	SIGNAL_IGNORE(SIGPIPE);
	SIGNAL_HANDLE(SIGALRM, sig_alarm);	/* process agenda */
	SIGNAL_HANDLE(SIGUSR2, sig_iot);	/* dump queue info */

	/* call it to create the timeserver -- if possible */
	init_timeserver();

	queryipcinit();

	dirqueuescan(".", dq);

	vtxprep_skip_lock = 0;
	syncweb(dq, tp);

	if (dlyverbose) verbose = dlyverbose;

	canexit = 1;
	SIGNAL_HANDLE(SIGTERM, sig_exit);	/* split */
#ifdef	MALLOC_TRACE
	mal_leaktrace(1);
#endif	/* MALLOC_TRACE */

	/* If we do a sync-start, we are synchronous.. */

	if (syncstart) {
	  int startcount;
	  dirqueuescan(".", dq);
	  startcount = dq->wrkcount;
	  while (dq->wrkcount > 0 && !mustexit) {
	    syncweb(dq, tp);
	    queryipccheck();
	  }
	  fprintf(stderr,"Synchronous startup completed, messages: %d (%d skipped) recipients: %d\n",
		  global_wrkcnt, startcount - global_wrkcnt, thread_count_recipients());
	  fprintf(stderr,"***********************************************************************\n");
	  syncstart = 0;
	}

	do {
	  time_t timeout;

	  mytime(&now);

	  canexit = 0;

	  if (now >= next_dirscan) {
	    /* Directory scan time for new jobs .. */
	    if (dirqueuescan(".", dq) < 150) {
	      /* If we have more things to scan, don't quit yet! */
	      next_dirscan = now + sweepinterval;	/* 10 seconds interval */
	      if (dq->wrkcount > 100)
		next_dirscan = now + 60; /* Lots of work, pick new jobs
					    less frequently */
	    }
	  }

	  if (now >= next_idlecleanup) {
	    next_idlecleanup = now + 20; /* 20 second interval */
	    idle_cleanup();
	  }

	  /* Submit one item from pre-scheduler-queue into the scheduler */
	  if (dq->wrkcount > 100) {
	    /* If more than 100 in queue, submit 20 in a burst.. */
	    int i;
	    for (i=0; i < 20; ++i)
	      syncweb(dq, tp);
	  }
	  if (dq->wrkcount > 0)
	    syncweb(dq, tp);

	  /* See when to timeout from mux() */
	  timeout = next_dirscan;

	  /* If we still have things in pre-scheduler queue... */
	  if (dq->wrkcount > 0)
	    timeout = now;

	  /* Submit possible new jobs (unless frozen) */
	  if (!freeze && !syncstart && doagenda() != 0)
	    timeout = now;	/* we still have some jobs avail for start */

	  gotalarm = 0;
	  canexit = 1;

	  /* mux on the stdout of children */
	  do {
	    if (dumpq) {
	      qprint(-1);
	      dumpq = 0;
	    }
	    if (rereadcf) {
	      tp = rereadconfig(tp, config);
	      rereadcf = 0;
	    }
	    mytime(&now);
	  } while (mux(timeout) == 0 && !gotalarm &&
		   dq->wrkcount == 0 && !mustexit && timeout > now);
	} while (!mustexit);

	/* Doing nicely we would kill the childs here, but we are not
	   such a nice people -- we just discard incore data, and crash out.. */

	sp_scan(ctl_free, NULL, spt_mesh[L_CTLFILE]);

#ifdef	MALLOC_TRACE
	mal_dumpleaktrace(stderr);
#endif	/* MALLOC_TRACE */
	if (mustexit)
		die(0, "signal");
	return 0;
}

static RETSIGTYPE sig_exit(sig)
int sig;
{
	
	if (querysocket >= 0) {		/* give up mailq socket asap */
		close(querysocket);
		querysocket = -1;
	}
	if (canexit)
		die(0, "signal");
	mustexit = 1;
}

static RETSIGTYPE sig_alarm(sig)
int sig;
{
	gotalarm = 1;
	SIGNAL_HANDLE(SIGALRM, sig_alarm);
}

#ifdef SIGUSR2
static RETSIGTYPE sig_iot(sig)
int sig;
{
	dumpq = 1;
	SIGNAL_HANDLE(SIGUSR2, sig_iot);
}
#endif

#ifdef SIGUSR1
static RETSIGTYPE sig_readcf(sig)
int sig;
{
	rereadcf = 1;
	SIGNAL_HANDLE(SIGUSR1, sig_readcf);
}
#endif

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

static int in_dirscanqueue(dq,ino)
	struct dirqueue *dq;
	long ino;
{
	int i;

	/* Return 1, if can find the "ino" in the queue */

	if (dq->wrkcount == 0 || dirscan_mesh == NULL) return 0;
	if (sp_lookup((u_long)ino,dirscan_mesh) != NULL) return 1;
	return 0;
}

static int dq_ctimecompare(b,a) /* we want oldest entry LAST */
const void *a; const void *b;
{
	const struct dirstatname **A = (const struct dirstatname **)a;
	const struct dirstatname **B = (const struct dirstatname **)b;

	return ((*A)->st.st_ctime - (*B)->st.st_ctime);
}

static int dirqueuescan(dir, dq)
	const char *dir;
	struct dirqueue *dq;
{
	DIR *dirp;
	struct dirent *dp;
	struct stat stbuf;
	char file[MAXNAMLEN+1];
	int newents = 0;
	struct dirstatname *dsn;

#if 0
	static time_t modtime = 0;

	/* Any changes lately ? */
	if (estat(dir, &stbuf) < 0)
	  return -1;	/* Could not stat.. */
	if (stbuf.st_mtime == modtime)
	  return 0;	/* any changes lately ? */
	modtime = stbuf.st_mtime;
#endif

if (verbose) { printf("dirqueuescan(dir='%s') ",dir); fflush(stdout); }

	/* Some changes lately, open the dir and read it */

	dirp = opendir(dir);
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
	  /* Scan filenames into memory */

	  if (!syncstart && newents > newents_limit)
	    break; /* At most XXX per one go */

	  if (dp->d_name[0] >= '0' &&
	      dp->d_name[0] <= '9') {
	    /* A file whose name STARTS with a number (digit) */

	    long ino = atol(dp->d_name);
	    if (in_dirscanqueue(dq,ino))
	      /* Already in pre-schedule-queue */
	      continue;

	    strcpy(file, dp->d_name);

	    /* We may have this file in processing state...  */
	    {
	      struct spblk *spl;
	      spl = sp_lookup((u_long)ino, spt_mesh[L_CTLFILE]);
	      if (spl != NULL) {
		/* Already in processing, don't touch.. */
		/*printf("File: %s active (not previously locked)\n",file);*/
		++vtxprep_skip_any;
		continue;
	      }
	    }


	    if (lstat(file,&stbuf) != 0 ||
		!S_ISREG(stbuf.st_mode)) {
	      /* Not a regular file.. Let it be there for the manager
		 to wonder.. */
	      continue;
	    }

	    /* Now store the entry */
	    dsn = (struct dirstatname*)emalloc(sizeof(*dsn)+strlen(file));
	    memcpy(&(dsn->st),&stbuf,sizeof(stbuf));
	    dsn->ino = ino;
	    strcpy(dsn->name,file);
	    sp_install(ino,(u_char*)dsn,0,dirscan_mesh);

	    if (dq->wrkspace <= dq->wrkcount) {
	      /* Increase the space */
	      dq->wrkspace += 8;
	      if (dq->stats == NULL)
		dq->stats = (struct dirstatname **)emalloc(sizeof(void*) *
							   dq->wrkspace);
	      else
		dq->stats = (struct dirstatname**)erealloc(dq->stats,
							   sizeof(void*) *
							   dq->wrkspace);
	    }
	    dq->stats[dq->wrkcount] = dsn;
	    dq->wrkcount += 1;
	    ++newents;
	  } /* ... end of "filename starts with a [0-9]" */
	}
	if (newents && dq->wrkcount > 1) {
	  /* Sort the dq->stats[] per file ctime -- LATEST on slot 0.
	     (we drain this queue from the END) */
	  qsort((void*)dq->stats,
		dq->wrkcount,
		sizeof(void*),
		dq_ctimecompare );
	}

#ifdef	BUGGY_CLOSEDIR
	/*
	 * Major serious bug time here;  some closedir()'s
	 * free dirp before referring to dirp->dd_fd. GRRR.
	 * XX: remove this when bug is eradicated from System V's.
	 */
	close(dirp->dd_fd);
#endif
	closedir(dirp);

	if (verbose) printf("wrkcount=%d new=%d\n",dq->wrkcount,newents);

	return newents;
}

static int syncweb(dq, cehead)
	struct dirqueue *dq;
	struct config_entry *cehead;
{
	struct stat *stbuf;
	char *file;
	struct spblk *spl;
	int wrkcnt = 0;
	long ino;

	/* Any work to do ? */
	if (dq->wrkcount == 0) return 0;

	/* Be responsive, check query channel */
	queryipccheck();

	/* Ok some, decrement the count to change it to index */
	dq->wrkcount -= 1;
	file  =   dq->stats[dq->wrkcount]->name;
	stbuf = &(dq->stats[dq->wrkcount]->st);
	ino   =   dq->stats[dq->wrkcount]->ino;
	/* Now we have pointers */

	/* Deletion from the  dirscan_mesh  should ALWAYS succeed.. */
	sp_delete(sp_lookup((u_long)ino,dirscan_mesh),dirscan_mesh);

	/* Sometimes we may get files already in processing
	   into our pre-schedule queue */

	/* Already in processing ? */
	spl = sp_lookup((u_long)ino, spt_mesh[L_CTLFILE]);
	if (spl == NULL) {
	  /* Not yet in processing! */
	  int fd;

	  /* Can open ? */
	  if ((fd = eopen(file, O_RDWR, 0)) < 0) {
	    if (getuid() == 0)
	      eunlink(file);	/* hrmpf! */
	  } else {
	    /* Ok, schedule! */
	    if (schedule(fd, file, cehead, ino) != NULL) {
	      /* Success, increment counters */
	      ++wrkcnt;
	    }
	  }
	}

	/* Free the pre-schedule queue entry */
	free(dq->stats[dq->wrkcount]);
	dq->stats[dq->wrkcount] = NULL;

	return wrkcnt;
}

void resync_file(file)
	char *file;
{
	struct stat stbuf;
	struct spblk *spl;
	long ino, fd;

	lstat(file,&stbuf);
	ino = atoi(file);

	/* Sometimes we may get reports of files
	   already deleted from processing.. */
	printf("Resyncing file \"%s\" (ino=%d)",file,ino);

	/* Already in processing ? */
	spl = sp_lookup((u_long)ino, spt_mesh[L_CTLFILE]);
	if (spl == NULL) {
	  printf(" .. not in processing database\n");
	  /* Not (anymore) in processing! */
	  return;
	}
	printf(" .. in processing db\n");

	/* Delete it... */
	cfp_free((struct ctlfile *)spl->data, spl);
	/* It will return into processing at next queue scan.. */
}


/*
 * 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.
 */
static struct ctlfile *schedule(fd, file, cehead, ino)
	int fd;
	char *file;
	struct config_entry *cehead;
	long ino;
{
	struct ctlfile *cfp;
	struct vertex *vp;

	/* read and process the control file */
	if ((cfp = vtxprep(slurp(fd, ino))) == NULL) {
	  if (!vtxprep_skip) {	/* Unless skipped.. */
	    eunlink(file);	/* everything here has been processed */
	    if (verbose)
	      printf("completed, unlink %s\n",file);
	    return NULL;
	  }
	  vtxprep_skip_any += vtxprep_skip;
	  return NULL;
	}
	if (cfp->head == NULL) {
	  ++global_wrkcnt;
	  unctlfile(cfp, 0);
	  return NULL;
	}
	for (vp = cfp->head; vp != NULL; vp = vp->next[L_CTLFILE]) {
	  vtxdo(vp, cehead, file);
	  if (cfp->vfp)
	    fseek(cfp->vfp, 0, 2);
	}
	/* If no verbose trace, don't keep contents in core!
	   this means HUGE memory save at busy machines! */
	if (cfp->vfp == NULL)
	  if (cfp->contents != NULL) {
	    free(cfp->contents);
	    cfp->contents = NULL;
	  }

	sp_install(cfp->id, (u_char *)cfp, 0, spt_mesh[L_CTLFILE]);
	++global_wrkcnt;
	return cfp;
}

/*
 * slurp() gets in the job-descriptor file, and does initial
 *         parsing on it.
 */

struct ctlfile *
slurp(fd, ino)
	int fd;
	long ino;
{
	register char *s;
	register int i;
	char *contents;
	long *offset, *ip, *lp;
	int offsetspace;
	struct stat stbuf;
	struct ctlfile *cfp;

	if (fd < 0)
	  return NULL;
	if (efstat(fd, &stbuf) < 0) {
	  close(fd);
	  return NULL;
	}
	if (!S_ISREG(stbuf.st_mode)) {
	  close(fd);
	  return NULL;	/* XX: give error */
	}
	contents = emalloc(stbuf.st_size+1);
	if (eread(fd, contents, stbuf.st_size) != stbuf.st_size) { /* slurp */
	  close(fd);
	  return NULL;
	}
	contents[stbuf.st_size] = 0;

	cfp = (struct ctlfile *)emalloc(sizeof(struct ctlfile));
	memset((void*)cfp, 0, sizeof(struct ctlfile));

	cfp->fd = fd;
	cfp->uid = stbuf.st_uid;
	cfp->envctime = stbuf.st_ctime;
	cfp->contents = contents;
	/* 
	   cfp->vfp = NULL;
	   cfp->head = NULL;
	   cfp->mark = V_NONE;
	   cfp->haderror = 0;
	   cfp->mid      = NULL;
	   cfp->envid    = NULL;
	   cfp->logident = NULL;
	   cfp->erroraddr = NULL;
	   cfp->msgbodyoffset = 0;
	 */

	/* go through the file and mark it off */
	i = 0;
	offsetspace = 100;
	offset = (long*)emalloc(sizeof(long)*offsetspace);
	offset[i++] = 0L;
	for (s = contents; s - contents < stbuf.st_size; ++s) {
	  if (*s == '\n') {
	    *s++ = '\0';
	    if (s - contents < stbuf.st_size) {
	      if (i >= offsetspace-1) {
		offsetspace += 20;
		offset = (long*)erealloc(offset,sizeof(long)*offsetspace);
	      }
	      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 */
		}
	      } else if (*s == _CF_BODYOFFSET) {
		cfp->msgbodyoffset = atoi(s+2);
	      } else if (*s == _CF_DSNENVID) {
		/* cfp->envid = strsave(s+2); */
	      } else if (*s == _CF_MESSAGEID) {
		/* cfp->mid = strsave(s+2); */
	      } else if (*s == _CF_LOGIDENT) {
		/* cfp->logident = strsave(s+2); */
	      } else if (*s == _CF_ERRORADDR) {
		/* cfp->erroraddr = strsave(s+2); */
	      } else if (*s == _CF_TURNME) {
		/* Umm... it is a bit complex */
		printf("A TURNME request for target '%s'\n",s+2);
	      }
	    }
	  }
	}
	cfp->nlines = i;
	/* closing fd must be done in vtxprep(), so we can unlock stuff easy */
	cfp = (struct ctlfile *)erealloc((void*)cfp,
					 (u_int) (sizeof(struct ctlfile) +
						  i * (sizeof offset[0])));
	lp = &(cfp->offset[0]);
	ip = &(offset[0]);
	/* copy over the offsets */
	while (--i >= 0)
	  *lp++ = *ip++;
	cfp->id = ino;
	/* cfp->mid = NULL; */
	free(offset);	/* release the block */
	return cfp;
}

struct offsort {
	long	offset;
	int	myidx;
	long	headeroffset;
	long	drptoffset;
	char	*sender;
	char	*dsnrecipient;
};

/* ``bcfcn'' is used by the qsort comparison routine,
   bcp points to the control file bytes
 */

static char *bcp;

static int bcfcn(a, b)
     const void *a, *b;
{
	const struct offsort *aa = a, *bb = b;
	return cistrcmp(bcp + aa->offset, bcp + bb->offset);
}


static int
lockverify(cfp,cp)		/* Return 1 when lock process does not exist */
	struct ctlfile *cfp;	/* Call only when the lock is marked active! */
	char *cp;
{
	char	lockbuf[1+_CFTAG_RCPTPIDSIZE];
	int	lockpid;
	int	sig = 0;

#ifdef SIGCONT	/* OSF/1 acts differently if we use SIGNAL 0 :-( */
	sig = SIGCONT;
#endif

	++cp;
	if (!(*cp == ' ' ||
	      (*cp >= '0' && *cp <= '9'))) return 1; /* Old-style entry */
	memcpy(lockbuf,cp,_CFTAG_RCPTPIDSIZE);
	lockbuf[sizeof(lockbuf)-1] = 0;
	if (sscanf(lockbuf,"%d",&lockpid) != 1) return 1; /* Bad value ? */
	if (kill(lockpid,sig) != 0) return 1; /* PID does not exist, or
					       other error.. */
	fprintf(stderr,"lockverify: Lock with PID=%d is active on %s:%s\n",
		lockpid, cfp->mid, cp+_CFTAG_RCPTPIDSIZE);
	return 0;	/* Locking PID does exist.. */
}

/*
 *  The  vtxprep() does deeper analysis on jobs described at the file.
 *  It verifies possible locks (if they are still valid), and gathers
 *  all of the information regarding senders and recipients.
 *
 *  All "recipient"-lines are sorted to ease searching of vertices with
 *  identical channel, and host definitions.  If there are more than one
 *  recipient with given (channel, host)-tuple, all such recipients are
 *  wrapped into same vertex node with its respective ``recipient group''.
 *
 */
static struct ctlfile *vtxprep(cfp)
	struct ctlfile *cfp;
{
	register int i, opcnt;
	register long *lp;
	int svn;
	char *cp, *channel, *host, *l_channel, *l_host;
	char *echannel, *ehost, *l_echannel, *l_ehost, mfpath[100], flagstr[2];
	char *latest_sender = NULL;
	char *senderchannel = NULL;
	struct spblk *spl;
	struct vertex *vp, *pvp, **pvpp, *head;
	struct stat stbuf;
	struct offsort *offarr;
	int offspc;

	if (cfp == NULL)
	  return NULL;

	channel = host = NULL;

	/* copy offsets into local array */
	offspc = 16;
	offarr = (struct offsort *)emalloc(offspc * sizeof(struct offsort));

	opcnt = 0;
	svn = 0;
	vtxprep_skip = 0;
	lp = &cfp->offset[0];
	for (i = 0; i < cfp->nlines; ++i, ++lp) {
	  cp = cfp->contents + *lp + 1;
	  if (*cp == _CFTAG_LOCK) {
	    /*
	     * This can happen when we restart the scheduler, and
	     * some previous transporter is still running.
	     *
	     */
	    if (!lockverify(cfp,cp)) {
	      /*
	       * IMO we are better off by forgetting for a while that
	       * this spool-file exists at all.  Thus very least we
	       * won't errorneously delete it.
	       */
	      close(cfp->fd);	/* Was opened on  schedule() */
	      if (cfp->vfp != NULL) {
		fprintf(cfp->vfp,
			"New scheduler: Skipped a job-file because it is held locked by PID=%6.6s\n",cp+1);
		fclose(cfp->vfp);
	      }
	      cfp_free(cfp, NULL);
	      ++vtxprep_skip;
	      ++vtxprep_skip_lock;
	      free(offarr);
	      return NULL;
	    }
	    *cp = _CFTAG_NORMAL; /* unlock it */
	    lockaddr(cfp->fd, NULL, (long) (cp - cfp->contents),
		     _CFTAG_LOCK, _CFTAG_NORMAL);
	  }
	  /* Calculate summary info */
	  if (cp[-1] == _CF_RECIPIENT) {
	    cfp->rcpnts_total += 1;
	    if (*cp == _CFTAG_NOTOK)   cfp->rcpnts_failed += 1;
	    else if (*cp == _CFTAG_OK) cfp->rcpnts_work   += 1;
	  }
	  if (*cp == _CFTAG_NORMAL ||
	      *cp == '\n' /* This appears for msg-header entries.. */ ) {
	    --cp;
	    switch (*cp) {
	    case _CF_SENDER:
	      ++cp;
	      while (*cp == ' ') ++cp;
	      senderchannel = cp;
	      while (*cp != 0 && *cp != ' ') ++cp; 
	      if (*cp == ' ') *cp++ = 0;
	      while (*cp == ' ') ++cp;
	      while (*cp != 0 && *cp != ' ') ++cp; while (*cp == ' ') ++cp;
	      latest_sender = cp;
	      while (*cp != 0 && *cp != ' ') ++cp;  *cp = 0; /* We assume: No spaces at the sender addr! */
	      if (cfp->erroraddr)  free(cfp->erroraddr);
	      cfp->erroraddr = strsave(latest_sender);
	      if (cfp->errorchan)  free(cfp->errorchan);
	      cfp->errorchan = strsave(senderchannel);
	      break;
	    case _CF_RECIPIENT:
	      if (opcnt >= offspc-1) {
		offspc *= 2;
		offarr = (struct offsort *)erealloc(offarr,
						    sizeof(struct offsort) *
						    offspc);
	      }
	      offarr[opcnt].offset = *lp + 2;
	      cp += 2;
	      if (*cp == ' ' || (*cp >= '0' && *cp <= '9')) {
		/* New PID locking scheme.. */
		offarr[opcnt].offset += _CFTAG_RCPTPIDSIZE;
	      }
	      offarr[opcnt].myidx = i;
	      offarr[opcnt].headeroffset = -1;
	      offarr[opcnt].drptoffset = -1;
	      offarr[opcnt].sender = latest_sender;
	      ++opcnt;
	      break;
	    case _CF_RCPTNOTARY:
	      /* XX: IETF-NOTARY-DRPT DATA! */
	      offarr[svn].drptoffset = *lp + 2;
#if 0 /* ---------------------------------------------------------------- */
	      /* This fragment is pulled from transports/libta/ctlopen.c */
	      ++s;
	      while (*s) {
		while (*s && (*s == ' ' || *s == '\t')) ++s;
		if (cistrncmp("RET=",s,4)==0) {
		  s += 4;
		  if (cistrncmp("YES",s,3)==0) {
		    rp->dsnflags |= _DSN_RET_YES;
		    s += 3;
		  } else if (cistrncmp("NO",s,2)==0) {
		    rp->dsnflags |= _DSN_RET_NO;
		    s += 2;
		  } else 
		    /* Not YES/NO ??? */
		    ;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  continue;
		}
		if (cistrncmp("NOTIFY=",s,7)==0) {
		  s += 7;
		  if (cistrncmp("SUCCESS",s,7)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_SUCCESS;
		    s += 7;
		  } else if (cistrncmp("FAILURE",s,7)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_FAILURE;
		    s += 7;
		  } else if (cistrncmp("ALWAYS",s,6)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_ALWAYS;
		    s += 6;
		  } else if (cistrncmp("NEVER",s,5)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_NEVER;
		    s += 5;
		  } else
		    /* None of the above ?? */
		    ;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  continue;
		}
		if (cistrncmp("ORCPT=",s,6)==0) {
		  s += 6;
		  rp->orcpt = s;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  if (*s) *s++ = 0;
		  continue;
		}
	      }
#endif /* ---------------------------------------------------------------- */
	      break;
	    case _CF_MSGHEADERS:
	      for (/* we count up.. */; svn < opcnt; ++svn)
		offarr[svn].headeroffset = *lp + 2;
	      break;
	    case _CF_DSNENVID:
	      if (cfp->envid) free(cfp->envid); /* shouldn't happen.. */
	      cfp->envid = strsave(cp+2);
	      break;
	    case _CF_DIAGNOSTIC:
	      cfp->haderror = 1;
	      break;
	    case _CF_MESSAGEID:
	      if (cfp->mid != NULL) free(cfp->mid); /* shouldn't happen.. */
	      cfp->mid = strsave(cp+2);
	      break;
	    case _CF_BODYOFFSET:
	      cfp->msgbodyoffset = atoi(cp+2);
	      break;
	    case _CF_LOGIDENT:
	      if (cfp->logident) free(cfp->logident); /* shouldn't happen..*/
	      cfp->logident = strsave(cp+2);
	      break;
	    case _CF_ERRORADDR:
	      if (cfp->erroraddr) free(cfp->erroraddr); /* could happen */
	      cfp->erroraddr = strsave(cp+2);
	      break;
	    case _CF_OBSOLETES:
	      deletemsg(cp+2, cfp);
	      break;
	    case _CF_VERBOSE:

	      setreuid(0, cfp->uid);

	      cfp->vfp = fopen(cp+2, "a+");

	      setreuid(0, 0);

	      if (cfp->vfp != NULL) {
		fseek(cfp->vfp, (long)0, 2);
		setvbuf(cfp->vfp, (char *)NULL,	_IOLBF, 0);
	      }
	      if (cfp->vfp != NULL && cfp->mid != NULL)
		fprintf(cfp->vfp,
			"scheduler processing %s\n",
			cfp->mid);
	      break;
	    case _CF_TURNME:
	      printf("TURNME: %s\n",cp+2);
	      strlower(cp+2);
	      turnme(cp+2);
	      break;
	    }
	  }
	}
	close(cfp->fd);	/* closes the fd opened in schedule() */
	if (cfp->mid != NULL)
	  sprintf(mfpath, "../%s/%s", QUEUEDIR, cfp->mid);
	if (cfp->mid == NULL || cfp->logident == NULL
	    || estat(mfpath, &stbuf) < 0) {
	  if (cfp->vfp != NULL) {
	    fprintf(cfp->vfp,
		    "aborted due to missing information\n");
	    fclose(cfp->vfp);
	  }
	  cfp_free(cfp, NULL);
	  free(offarr);
	  return NULL;
	}
	cfp->ctime = stbuf.st_mtime; /* instead of ctime, we use mtime
					at the queue-dir accesses, this
					way we can move the spool to
					another machine, and run things
					in there, and still have same
					expiration times.. */
	cfp->fd = -1;
	/* sort it to get channels and channel/hosts together */
	bcp = cfp->contents;
	qsort((char *)offarr, opcnt, sizeof (struct offsort), bcfcn);
	/*
	 * Loop through them; whenever either channel or host changes,
	 * make a new vertex. All such vertices must be linked together.
	 */
	strcpy(flagstr," ");
	l_channel = l_echannel = l_host = l_ehost = flagstr;
	svn = 0;
	pvp = NULL;
	head = NULL;
	pvpp = &head;
	for (i = 0; i < opcnt; ++i) {
	  channel = bcp + offarr[i].offset /* - 2 */;
	  if ((echannel = strchr(channel, ' ')) == NULL) /* error! */
	    continue;
	  *echannel = '\0';
	  strlower(channel);
	  host = echannel + 1;
	  while (*host == ' ') ++host;
#if 1
	  /* [mea]   channel ((mx.target)(mx.target mx.target)) rest.. */
	  cp = host;
	  if (*cp == '(') {
	    while(*cp == '(')
	      ++cp;
	    while(*cp && *cp != ' ' && *cp != '\t' && *cp != ')')
	      ++cp;
	    if (*cp)
	      ehost = cp;
	    else
	      continue;		/* error! */
	    /* Ok, found ehost, now parsing past parenthesis.. */
	    cp = host;
	    while(*host == '(')  ++host;
	    if (*cp == '(') {
	      ++cp;
	      while(*cp == '(') {
		/* Find ending ')', and skip it */
		while(*cp && *cp != ')') ++cp;
		if (*cp == ')') ++cp;
	      }
	      /* Ok, scanned past all inner parenthesis, now ASSUME
		 next one is ending outer parenthesis, and skip it */
	      ++cp;
	    }
	    if (*cp != ' ' && *cp != '\t')
	      continue;		/* Not proper separator.. error! */
	  } else
#endif
	    if ((ehost = strchr(host, ' ')) == NULL) /* error! */
	      continue;

	  *ehost = '\0';
	  strlower(host);
	  /* compare with the last ones */
	  if (strcmp(channel, l_channel) || strcmp(host, l_host)) {
	    /* wrap and tie the old vertex node */
	    if (i != svn) {
	      u_int alloc_size = (u_int) (sizeof (struct vertex) +
					  (i - svn - 1) * sizeof (int));
	      vp = (struct vertex *)emalloc(alloc_size);
	      memset((char*)vp, 0, alloc_size);
	      vp->cfp          = cfp;
	      vp->next[L_CTLFILE] = NULL;
	      vp->prev[L_CTLFILE] = pvp;
#if 0
	      vp->message      = NULL;
	      vp->retryindex   = 0;
	      vp->nextitem     = NULL;
	      vp->previtem     = NULL;
	      vp->wakeup       = 0;
	      vp->proc         = NULL;
	      vp->attempts     = 0;
	      vp->notary       = NULL;
#endif
	      vp->ngroup       = i - svn;
	      /* vp->sender       = strsave(offarr[svn].sender); */
	      vp->headeroffset = offarr[svn].headeroffset; /*They are similar*/
	      vp->drptoffset   = offarr[svn].drptoffset;
	      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'ecipient  line into the current vertex */
	  /* restore the characters */
	  *l_echannel = *l_ehost = ' ';
	  l_echannel  = echannel;
	  l_ehost     = ehost;
	  l_channel   = channel;
	  l_host      = host;
	} /* for( .. i < opcnt .. ) */

	/* wrap and tie the old vertex node (this is a copy of code above) */
	if (i != svn) {
	  u_int alloc_size = (u_int) (sizeof (struct vertex) +
				      (i - svn - 1) * sizeof (int));
	  vp = (struct vertex *)emalloc(alloc_size);
	  memset((void*)vp, 0, alloc_size);
	  vp->cfp = cfp;
	  vp->next[L_CTLFILE] = NULL;
	  vp->prev[L_CTLFILE] = pvp;
#if 0
	  vp->message = NULL;
	  vp->retryindex = 0;
	  vp->nextitem = NULL;
	  vp->previtem = NULL;
	  vp->proc = NULL;
	  vp->wakeup = 0;
	  vp->attempts     = 0;
	  vp->notary       = NULL;
#endif
	  vp->ngroup = i - svn;
	  /* vp->sender = strsave(offarr[snv].sender); */
	  vp->headeroffset = offarr[svn].headeroffset; /* Just any of them will do */
	  vp->drptoffset = offarr[svn].drptoffset;
	  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("\t%s\n", cfp->contents+cfp->offset[vp->index[i]]);
	   }
	*/
	cfp->head = head;
	free(offarr);
	if (verbose) {
	  int completed = cfp->rcpnts_total - cfp->rcpnts_work -
			  cfp->rcpnts_failed;
	  printf("vtxprep: msg %s rcptns total %d work %d failed %d done %d\n",
		 cfp->mid, cfp->rcpnts_total, cfp->rcpnts_work,
		 cfp->rcpnts_failed, completed );
	}
	return cfp;
}

/*
 *  The  vtxmatch()  is a subroutine to  vtxdo()  matching for
 *  scheduler definition entries from the scheduler configuration table.
 *
 */

static int vtxmatch(vp, tp)
	struct vertex *vp;
	struct config_entry *tp;
{
	/* if the channel doesn't match, there's no hope! */
	if (verbose>1)
	  printf("ch? %s %s\n", vp->orig[L_CHANNEL]->name, tp->channel);
	if (tp->channel[0] == '*' && tp->channel[1] == '\0')
	  return 0; /* Never match the defaults entry! */
	if (!globmatch(tp->channel, vp->orig[L_CHANNEL]->name))
	  return 0;

	if (!(tp->host == NULL || tp->host[0] == '\0' ||
	      (tp->host[0] == '*' && tp->host[1] == '\0'))) {
	  if (!globmatch(tp->host, vp->orig[L_HOST]->name))
	    return 0;
	}

	if (verbose>1)
	  printf("host %s %s\n", vp->orig[L_HOST]->name, tp->host);

	return 1;
}


static void ce_fillin(thg,cep)
	struct threadgroup *thg;
	struct config_entry *cep;
{
	struct config_entry *ce = &(thg->ce);

	defaultconfigentry( ce,cep );

	if (cep->interval != -1) ce->interval = cep->interval;
	if (cep->idlemax  != -1) ce->idlemax  = cep->idlemax;
	if (cep->expiry   != -1) ce->expiry   = cep->expiry;
	ce->expiryform     = cep->expiryform;
	if (cep->uid      != -1) ce->uid = cep->uid;
	if (cep->gid      != -1) ce->gid = cep->gid;
	if (cep->maxkids  != -1) ce->maxkids = cep->maxkids;
	if (cep->maxkidChannel != -1) ce->maxkidChannel = cep->maxkidChannel;
	if (cep->maxkidThreads != -1) ce->maxkidThreads = cep->maxkidThreads;
	if (cep->nretries > 0) {
	  ce->nretries = cep->nretries;
	  ce->retries  = cep->retries;
	}
	if (cep->command != NULL) {
	  ce->command = cep->command;
	  ce->argv    = cep->argv;
	}
	ce->flags |= cep->flags; /* XX: Grumble.. additive only.. */
	ce->host   = cep->host;
	if (cep->skew > 0) ce->skew = cep->skew;

	if (ce->interval == -1)
	  ce->interval = 3600;
	if (ce->idlemax == -1)
	  ce->idlemax = ce->interval * 3;
	if (ce->maxkids == -1)
	  ce->maxkids = global_maxkids;
	if (ce->maxkidChannel == -1)
	  ce->maxkidChannel = global_maxkids;
	if (ce->maxkidThreads == -1)
	  ce->maxkidThreads = global_maxkids;
}


/*
 *  The  vtxdo()  tries thru all scheduler configuration entries
 *  looking for a matching one which to fill in for the input vertex.
 *
 *  In the end it calls  reschedule()  to place the vertex into scheduling
 *  queues (threads)
 *
 */

static void vtxdo(vp, cehead, path)
	struct vertex *vp;
	struct config_entry *cehead;
	char *path;
{
	struct config_entry *tp;
	int n;
	int cnt = 0;

	/*
	 * 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) {
	  ++cnt;
	  if (vtxmatch(vp, tp)) {
	    /* tp points to the selected config entry */
	    n = 1;
	    break;
	  }
	}
	if (n == 0) {
	  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;
	}
	if (verbose)
	  printf("Matched %dth config entry with: %s/%s\n", cnt,
		 vp->orig[L_CHANNEL]->name, vp->orig[L_HOST]->name);

	/* set default values */
	if (tp->expiry > 0)
	  vp->ce_expiry = tp->expiry + vp->cfp->ctime;
	else
	  vp->ce_expiry = 0;

	thread_linkin(vp,tp,cnt,ce_fillin);

	if (verbose>1)
	  vtxprint(vp);
}


int vtxredo(spl)
        struct spblk *spl;
{
        struct ctlfile *cfp = (struct ctlfile *)spl->data;
	struct vertex *vp;

        /* assert cfp != NULL */
	for (vp = cfp->head ; vp != NULL ; vp = vp->next[L_CTLFILE]) {
	  vtxdo(vp, rrcf_head, NULL);
	}
        return 0;
}


/* Shell-GLOB-style matching */
static int globmatch(pattern, string)
	register char	*pattern;
	register char	*string;
{
	while (1) {
	  switch (*pattern) {
	  case '*':
	    ++pattern;
	    if (*pattern == 0) {
	      /* pattern ended with '*', we can accept any string trail.. */
	      return 1;
	    }
	    /* We do 'common case' optimization here, but will loose some
	       performance, if somebody gives '*foo*' as a pattern.. */
	    {
	      char *p = pattern;
	      int i = 0, c;
	      while ((c = *p++)) {
		/* Scan for special chars in pattern.. */
		if (c == '*' || c == '[' || c == '\\' || c == '?') {
		  i = 1; /* Found! */
		  break;
		}
	      }
	      if (!i) { /* No specials, match from end of string */
		int len = strlen(string);
		i = strlen(pattern);
		if (i > len) return 0; /* Tough.. pattern longer than string */
		if (strcmp(string +(len-i),pattern) == 0)
		  return 1; /* MATCH! */
	      }
	    }
	    do {
	      if (globmatch(pattern, string))
		return 1;
	    } while (*string++ != '\0');
	    return 0;
	  case '\\':
	    ++pattern;
	    if (*pattern == 0 ||
		*pattern != *string)
	      return 0;
	    break;
	  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.
 */
static void link_in(flag, vp, s)
	int flag;
	struct vertex *vp;
	u_char *s;
{
	struct web *wp = web_findcreate(flag,s);

	wp->linkcnt += 1;

	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;
}

/*
 * time-string to be prepended to logged messages
 */
char *
timestring()
{
	static char timebuf[40];
	struct tm *tp;

	mytime(&now);
	tp = localtime(&now);
	sprintf(timebuf,"%d%02d%02d%02d%02d%02d",
		tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday,
		tp->tm_hour, tp->tm_min, tp->tm_sec);
	return timebuf;
}

#if defined(HAVE_MMAP)
struct timeserver {
	int	pid;
#ifdef HAVE_SELECT
	struct timeval tv;
#else
	time_t	time_sec;
#endif
#define MAPSIZE 16*1024
};
struct timeserver *timeserver_segment = NULL;
static void init_timeserver()
{
	int ppid;

#if !defined(MAP_ANONYMOUS) || defined(__linux__)
	/* must have a file ? (SunOS 4.1, et.al.) */
	int fd = -1;
	char blk[1024];
	int i;
	FILE *fp = tmpfile();

	if (fp) {
	  fd = dup(fileno(fp));
	  fclose(fp);
	}
	if (fd < 0) return; /* Brr! */ 

	memset(blk,0,sizeof(blk));
	for (i=0; i < MAPSIZE; i += sizeof(blk))
	  write(fd,blk,sizeof(blk));
	lseek(fd,0,0);

#ifndef MAP_FILE
# define MAP_FILE 0 /* SunOS 4.1 does not have this */
#endif
	timeserver_segment = (void*)mmap(NULL, MAPSIZE, PROT_READ|PROT_WRITE,
					 MAP_FILE|MAP_SHARED, fd, 0);
	close(fd);
	
#else
#ifndef MAP_VARIABLE
# define MAP_VARIABLE 0 /* Some system have MAP_ANONYMOUS, but
			   no MAP_VARIABLE.. */
#endif
	/* This MAP_ANONYMOUS does work at DEC OSF/1 ... */
	timeserver_segment = (void*)mmap(NULL, MAPSIZE, PROT_READ|PROT_WRITE,
					 MAP_VARIABLE|MAP_ANONYMOUS|MAP_SHARED,
					 -1, 0);
#endif
	if (-1L == (long)timeserver_segment
	    || timeserver_segment == NULL) {
	  perror("mmap() of timeserver segment gave");
	  timeserver_segment = NULL;
	  return; /* Brr.. */
	}

	ppid = fork();
	if (ppid > 0) {
	  timeserver_pid = ppid;
	  timeserver_segment->pid = ppid;
	  return;
	}
	if (ppid < 0) return; /* Error ?? brr.. */

	ppid = getppid(); /* who is our parent ? */

	for(;;) {
#ifdef HAVE_SELECT
	  struct timeval tv;
	  int rc;

	  tv.tv_sec = 1;
	  tv.tv_usec = 0;

	  gettimeofday(&timeserver_segment->tv, NULL);

	  rc = select(0,NULL,NULL,NULL,&tv);
#else
	  time(&timeserver_segment->time_sec);
	  sleep(1);
#endif
	  /* Is the parent still alive ?? */
	  if (kill(ppid, 0) != 0)
	    break; /* No ?? Out! */
	}
	_exit(0);
}
#else
static void init_timeserver()
{
  /* Do nothing.. */
}

#endif

time_t mytime(timep)
time_t *timep;
{
#if defined(HAVE_MMAP)
	if (timeserver_pid) {
	  time_t t;
#ifdef HAVE_SELECT
	  t = timeserver_segment->tv.tv_sec;
#else
	  t = timeserver_segment->time_sec;
#endif
	  if (timep != NULL)
	    *timep = t;
	  return t;
	}
#endif
	return time(timep); /* The classical old version.. */
}
