[mod.sources] v07i063: Nag reminder service, Part02/02

sources-request@mirror.TMC.COM (11/10/86)

Submitted by: rtech!daveb@rtech.uucp (Dave Brower)
Mod.sources: Volume 7, Issue 63
Archive-name: nag/Part02

#!/bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #!/bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	gdate.c
#	nag.c
export PATH; PATH=/bin:$PATH
echo shar: extracting "'gdate.c'" '(13521 characters)'
if test -f 'gdate.c'
then
	echo shar: over-writing existing file "'gdate.c'"
fi
sed 's/^X//' << \SHAR_EOF > 'gdate.c'
X/*
X * routines to turn a date from various formats to other formats
X *
X *	Primary interesting routine is gdate() which eats a date
X *	string of several common formats (see comment) and
X *	fills in a standard UNIX tm structure.
X *
X *	Barry Shein, Boston University
X *
X *	if you compile it -DDEBUG (with DEBUG defined) it will
X *	pull in a main() routine to run standalone for testing.
X *
X *	NOTE:
X *
X *	Barry's gdate was broken by a 1-off error; tm_mon is kept
X *	in the range 0..11 instead of 1..12.  Also, his totime was
X *	broken, so I've deleted it and use the tm_to_time() function
X *	from mod.sources.
X *
X *
X * Defines the functions:
X *
X * lcase() -- convert a char to lower case
X * dstring() -- get digit string from sp into bp (buffer) returning new sp
X * skipw() -- skip white space returning updated ptr
X * prefix() -- return how many chars of s1 prefix s2
X * find() -- look up str in list for non-ambiguous (prefix) match
X * lookup() -- like find but demands exact match
X * extract() -- extract a token
X * fill() -- fill up an area with a value (eg. zeros)
X *
X * gdate() -- convert a date/time string to a tm structure.
X * gtime() -- convert time string to a tm structure.
X *
X * days() -- how many days were in a year.
X * jan1() -- return day of the week of jan 1 of given year
X * dowk() -- insert day of week given year and day of year into tm struct.
X * doyr() -- insert partial day of year given yr, mon and mday into tm struct.
X *
X * leap() -- Return 1 if `y' is a leap year, 0 otherwise.
X * ndays() -- number of days between UNIX epoch and time in a tm struct.
X * tm_to_time() -- Convert a tm struct to a time_t.
X */
X 
X#include <stdio.h>
X#include <ctype.h>
X#include <sys/types.h>
X
X#ifdef SYS5
X
X#	define	time_t	long	/* SV is inconsistent, so go with lcd */
X
X#	include <time.h>
X#	include <sys/times.h>
X#	include <string.h>
X
X# else				/* BSD */
X
X#	include <sys/time.h>
X#	include <sys/timeb.h>
X#	include <strings.h>
X
X#endif
X
X/*----------------------------------------------------------------
X *
X * Manifest constants
X *
X */
X
X#define MAXTOK 20
X#define AMBIG  -1	/* ambiguous month */
X#define FALSE  -2	/* bad syntax	   */
X
X/*----------------------------------------------------------------
X *
X *	static and global Data
X *
X */
X
Xchar *months[] = {
X  "january", "february", "march", "april", "may", "june", "july",
X  "august", "september", "october", "november", "december", 0
X} ;
X  
Xchar *dow[] = {
X    "sunday", "monday", "tuesday", "wednesday", "thursday",
X    "friday", "saturday", 0
X} ;
X    
X    /*
X     *	known time-zone name list
X     */
Xchar *tz[] =
X{
X  "adt", "ast", "edt", "est", "cdt", "cst", "mst", "mdt",
X  "pdt", "pst", "gmt",
X  0
X} ;
X
Xchar mdays[] = {
X  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
X} ;
X
X/*----------------------------------------------------------------
X *
X *	Utility functions
X *
X */
X
X/*
X * lcase() -- convert a char to lower case
X */
Xlcase(c) register char c ;
X{
X  return(isupper(c) ? tolower(c) : c) ;
X}
X
X/*
X * dstring() -- get digit string from sp into bp (buffer) returning new sp
X */
Xchar *
Xdstring(bp,sp)
X     register char *sp, *bp  ;
X{
X  register int i = 0 ;
X  while(isdigit(*sp))
X    if(i++ == MAXTOK) break ;
X    else *bp++ = *sp++ ;
X  *bp = '\0' ;
X  return(sp) ;
X}
X
X/*
X * skipw() -- skip white space returning updated ptr
X */
Xchar *
Xskipw(sp) register char *sp ;
X{
X  while(isspace(*sp) || *sp == '-') ++sp ;
X  return(sp) ;
X}
X
X/*
X * prefix() -- return how many chars of s1 prefix s2
X */
Xprefix(s1,s2) register char *s1, *s2 ;
X{
X  register int i = 0  ;
X  
X  for(; *s1 == *s2 ; s1++,s2++,i++)
X    if(*s2 == '\0') break ;
X  return(*s1 == '\0' ? i : 0) ;
X}
X
X/*
X * find() -- look up str in list for non-ambiguous (prefix) match
X */
Xfind(sp,lp) register char *sp, **lp ;
X{
X  int i,j,ambig = 0 ;
X  register int k ;
X  int ret  = FALSE ;
X  
X  for(i = 0,k = 0 ; *lp ; lp++,k++)
X    if((j  = prefix(sp,*lp)) > i)
X      {
X	ambig = 0 ;
X	i = j ;
X	ret = k + 1 ;
X      }
X    else if(j && (i == j)) ++ambig ;
X  return(ambig ? AMBIG : ret) ;
X}
X
X/*
X * lookup() -- like find but demands exact match
X */
Xlookup(sp,lp) register char *sp, **lp ;
X{
X  int i = 0 ;
X  
X  for(i=0 ; *lp ; lp++,i++)
X    if(strcmp(sp,*lp) == 0) return(i+1) ;
X  return(0) ;
X}
X
X/*
X * extract() -- extract a token
X */
Xchar *
Xextract(bp,sp) register char *bp, *sp ;
X{
X  register int i = 0 ;
X  
X  sp = skipw(sp) ;
X  for(; isalnum(*sp); sp++)
X    if(i++ == MAXTOK) break ;
X    else *bp++ = lcase(*sp) ;
X  *bp = '\0' ;
X  return(sp) ;
X}
X
X/*
X * fill() -- fill up an area with a value (eg. zeros)
X */
Xfill(cp,c,n) register char *cp, c ; register int n ;
X{
X  while(n--) *cp++ = c ;
X}
X
X/*----------------------------------------------------------------
X *
X *	gdate, gtime related.
X *
X */
X
Xchar *gdate() ;
Xchar *gtime() ;
X
X/*
X *	gdate(date_str_ptr,time_buf_ptr)
X *	(see CTIME(3) in UPM for second arg format)
X *	takes many reasonable date strings and translates to
X *	the time-buf structure (integers)
X *
X *	formats (eg.):
X *		oct 19, 1983
X *		OcT 19, 1983 12:43
X *		oCt 19, 1983 12:43:33
X *		oct 19 1983 ...
X *		19 oct 83 ....
X *		10/19/83 12:43:33
X *		19-OCT-83 12:43:00 (DEC style)
X *		Wed Oct 19 12:43 EST 83 (UNIX style)
X *		oct. 19, 83 1:44 pm est
X *		831019124333	(see date(1))
X *	some variations on those themes also.
X *
X *	BUGS: probably a few (maybe some in the eye of the beholder)
X *		does not set dst flag (unless 'EDT' is in string)
X */
Xchar *
Xgdate(sp,tp) register char *sp ; register struct tm *tp ;
X{
X  char buf[MAXTOK] ;
X  
X  fill(tp,'\0',sizeof *tp) ;
X  sp = skipw(sp) ;
X  if(isdigit(*sp))	/* either '7/12/83' or '12 jul 83' */
X    {
X      if(isdigit(sp[1]) && isdigit(sp[2])) /* UNIX yymmddhhmmss */
X	{
X	  buf[2] = '\0' ;
X	  (void)strncpy(buf,sp,2) ;
X	  tp->tm_year = atoi(buf) ;
X	  (void)strncpy(buf,sp += 2,2) ;
X	  tp->tm_mon = atoi(buf) - 1 ;
X	  sp += 2 ;
X	  if(!isdigit(*sp)) goto badday ;
X	  (void)strncpy(buf,sp,2) ;
X	  tp->tm_mday = atoi(buf) ;
X	  sp += 2 ;
X	  if(!isdigit(*sp)) goto check ;
X	  (void)strncpy(buf,sp,2) ;
X	  
X	  /* ??? formerly null effect "tp->tm_hour ;" */
X	  
X	  tp->tm_hour = atoi(buf) ;
X	  sp += 2 ;
X	  if(!isdigit(*sp)) goto check ;
X	  (void)strncpy(buf,sp,2) ;
X	  tp->tm_min = atoi(buf) ;
X	  sp += 2 ;
X	  if(!isdigit(*sp)) goto check ;
X	  (void)strncpy(buf,sp,2) ;
X	  tp->tm_min = atoi(buf) ;
X	  goto check ;
X	}
X      sp = dstring(buf,sp) ;
X      sp = skipw(sp) ;
X      if(*sp == '/')	/* must be '7/12/83' */
X	{
X	  if((tp->tm_mon = atoi(buf) - 1) < 0 || (tp->tm_mon > 11))
X	    {
X	      tp->tm_mon = FALSE ;
X	      goto badmon ;
X	    }
X	  sp = skipw(++sp) ;
X	  if(!isdigit(*sp)) goto badday ;
X	  sp = dstring(buf,sp) ;
X	  tp->tm_mday = atoi(buf) ;
X	  
X	  sp = skipw(sp) ;
X	  if(*sp != '/') goto badyr ;
X	  sp = skipw(++sp) ;
X	  if(!isdigit(*sp)) goto badyr ;
X	  sp = dstring(buf,sp) ;
X	  tp->tm_year = atoi(buf) ;
X	  
X	  sp = gtime(sp,tp) ;
X	}
X      else
X	{
X	  /*
X	   * must be '12 jul 83'
X	   */
X	  tp->tm_mday = atoi(buf) ;
X	  
X	  sp = extract(buf,sp) ;
X	  if((tp->tm_mon = find(buf,months)) < 0) goto badmon ;
X	  
X	  if(*sp == '.') ++sp ;
X	  sp = skipw(sp) ;
X	  
X	  if(!isdigit(*sp)) goto badyr ;
X	  sp = dstring(buf,sp) ;
X	  tp->tm_year = atoi(buf) ;
X	  
X	  sp = gtime(sp,tp) ;
X	}
X    }
X  else
X    {
X      int flag = 0 ;	/* used to indicate looking for UNIX style */
X      
X      /*
X       * either 'jul 12 83' or (UNIX) Wed jul 12 18:33 EST 1983
X       */
X      sp = extract(buf,sp) ;
X      if(find(buf,dow) > 0)
X	{
X	  sp = extract(buf,sp) ;
X	  ++flag ;
X	}
X      
X      if((tp->tm_mon = find(buf,months)) < 0) goto badmon ;
X      
X      if(*sp == '.') ++sp ;
X      sp = skipw(sp) ;
X      
X      if(!isdigit(*sp)) goto badday ;
X      sp = dstring(buf,sp) ;
X      tp->tm_mday = atoi(buf) ;
X      
X      sp = skipw(sp) ;
X      if(*sp == ',') sp++ ;
X      sp = skipw(sp) ;
X      
X      if(flag) sp = skipw(gtime(sp,tp)) ;
X      
X      if(!isdigit(*sp)) goto badyr ;
X      sp = dstring(buf,sp) ;
X      tp->tm_year = atoi(buf) ;
X      
X      if(!flag) sp = gtime(sp,tp) ;
X    }
X check:
X  /*
X   * check for ridiculous numbers
X   */
X  if(tp->tm_mday < 1) goto badday ;
X  if(tp->tm_mday > mdays[tp->tm_mon])
X    if(!((tp->tm_mon == 1) &&	/* check for Feb 29 */
X	 (tp->tm_mday == 29) && (days(tp->tm_year) == 365) ))
X      goto badday ;
X  if(tp->tm_year >= 1900) tp->tm_year -= 1900 ;
X  if(tp->tm_hour > 23)
X    {
X      tp->tm_hour = FALSE ;
X      return(sp) ;
X    }
X  if(tp->tm_min > 59)
X    {
X      tp->tm_hour = FALSE ;
X      return(sp) ;
X    }
X  if(tp->tm_sec > 59)
X    {
X      tp->tm_sec = FALSE ;
X      return(sp) ;
X    }
X  /*
X   *	fill in day of year, day of week (these calls must be
X   *	in this order as dowk() needs doyr()
X   */
X  
X  doyr(tp) ;
X  dowk(tp) ;
X  /*
X   * all done !
X   */
X  return(NULL) ;
X  badday :
X  tp->tm_mday = FALSE ;
X  return(sp) ;
X  badmon :
X  return(sp) ;
X  badyr :
X  tp->tm_year = FALSE ;
X  return(sp) ;
X}
X
X/*
X *  gtime() -- get  hh:mm:ss or equivalent into a tm struct.
X */
Xchar *
Xgtime(sp,tp) register char *sp ; register struct tm *tp ;
X{
X  char buf[MAXTOK],*cp ;
X  
X  sp = skipw(sp) ;
X  if(isdigit(*sp))
X    {
X      sp = dstring(buf,sp) ;
X      tp->tm_hour = atoi(buf) ;
X      sp = skipw(sp) ;
X      if(*sp == ':') sp = skipw(++sp) ;
X      else goto out ;
X      if(isdigit(*sp))
X	{
X	  sp = dstring(buf,sp) ;
X	  tp->tm_min = atoi(buf) ;
X	  sp = skipw(sp) ;
X	  if(*sp == ':') sp = skipw(++sp) ;
X	  else goto out ;
X	  if(isdigit(*sp))
X	    {
X	      sp = dstring(buf,sp) ;
X	      tp->tm_sec = atoi(buf) ;
X	    }
X	}
X    }
X  out :
X  sp = skipw(sp) ;
X  if(isalpha(*sp))	/* PM:AM or time zone or both */
X    {
X      cp = extract(buf,sp) ;
X      if(strcmp(buf,"am") == 0 || strcmp(buf,"pm") == 0)
X	{
X	  if(buf[0] == 'p' && tp->tm_hour < 12)
X	    tp->tm_hour += 12 ;
X	  sp = cp = skipw(cp) ;
X	  cp = extract(buf,cp) ;
X	}
X      if(lookup(buf,tz))
X	{
X	  if(buf[1] == 'd') tp->tm_isdst++ ;
X	  sp = skipw(cp) ;
X	}
X    }
X  return (sp);	
X}
X
X/*
X * days() -- how many days were in a year.
X *
X *	Ok, you were all wondering so here it is folks...
X *	THE LEAP YEAR CALCULATION
X *	note: does not take into account 1752.
X */
Xdays(y) register int y ;
X{
X  if(y < 1970) y += 1900 ;
X  if(((y % 4) == 0) && ( (y % 100) || ((y % 400)==0) )) y = 366 ;
X  else y = 365 ;
X  return(y) ;
X}
X
X
X/*
X * jan1() -- return day of the week of jan 1 of given year
X */
Xjan1(yr)
X{
X  register y, d;
X  
X  /*
X   *	normal gregorian calendar
X   *	one extra day per four years
X   */
X  
X  y = yr;
X  d = 4+y+(y+3)/4;
X  
X  /*
X   *	julian calendar
X   *	regular gregorian
X   *	less three days per 400
X   */
X  
X  if(y > 1800) {
X    d -= (y-1701)/100;
X    d += (y-1601)/400;
X  }
X  
X  /*
X   *	take care of weirdness at 1752.
X   */
X  
X  if(y > 1752)
X    d += 3;
X  
X  return(d%7);
X}
X
X/*
X * dowk() -- insert day of week given year and day of year into tm struct.
X */
Xdowk(tp) register struct tm *tp ;
X{
X  tp->tm_wday = (jan1(tp->tm_year+1900) + tp->tm_yday) % 7 ;
X}
X
X/*
X * doyr() -- insert partial day of year given yr and mon into tm struct.
X */
Xdoyr(tp) register struct tm *tp ;
X{
X  register int i,j ;
X
X  j = ((tp->tm_mon > 1) && (days(tp->tm_year) == 366)) ? 1 : 0 ;
X  for(i=1 ; i < tp->tm_mon ; i++)
X    j += mdays[i-1] ;
X  tp->tm_yday = j + tp->tm_mday - 1 ;
X}
X
X/*----------------------------------------------------------------
X *
X * tm_to_time related
X *
X */
X
X/*
X * leap() -- Return 1 if `y' is a leap year, 0 otherwise.
X */
Xstatic int
Xleap (y)
X     int y;
X{
X  y += 1900;
X  if (y % 400 == 0)
X    return (1);
X  if (y % 100 == 0)
X    return (0);
X  return (y % 4 == 0);
X}
X
X/*
X * ndays() -- number of days since UNIX epoch and time in a tm struct.
X */
Xstatic int
Xndays (p)
X     struct tm *p;
X{
X  register n = p->tm_mday;
X  register m, y;
X  register char *md = "\37\34\37\36\37\36\37\37\36\37\36\37";
X  
X  for (y = 70; y < p->tm_year; ++y) {
X    n += 365;
X    if (leap (y)) ++n;
X  }
X  for (m = 0; m < p->tm_mon; ++m)
X    n += md[m] + (m == 1 && leap (y));
X  return (n);
X}
X
X/*
X * tm_to_time() -- Convert a tm struct to a time_t.
X *
X * returns 0 if the time is before the UNIX epoch, 1/1/70 00:00:00
X */
Xtime_t
Xtm_to_time (tp)
X     struct tm *tp;
X{
X  register int m1, m2;
X  time_t t;
X  struct tm otm;
X  
X  /* special case date before epoch */
X  if( tp->tm_year < 70) return(0);
X  
X  t = (ndays (tp) - 1) * 86400L + tp->tm_hour * 3600L
X    + tp->tm_min * 60 + tp->tm_sec;
X  /*
X   * Now the hard part -- correct for the time zone:
X   */
X  otm = *tp;
X  tp = localtime (&t);
X  m1 = tp->tm_hour * 60 + tp->tm_min;
X  m2 = otm.tm_hour * 60 + otm.tm_min;
X  t -= ((m1 - m2 + 720 + 1440) % 1440 - 720) * 60L;
X  return (t);
X}
X
X/*----------------------------------------------------------------
X *
X * Test program related
X *
X */
X
X#if DEBUG
X/*
X *  test driver
X *	translates first arg from command line (argv[1])
X *	and dumps out structure built.
X */
Xusage(sp) char *sp ;
X{
X  fprintf(stderr,"Usage: %s date\n",sp) ;
X  exit(1) ;
X}
X
X/*
X * main() -- test the gdate and tm_to_time routines
X */
Xmain(argc,argv) int argc ; char **argv ;
X{
X  char *cp ;
X  struct tm tm ;
X  time_t t,t2 ;
X  char *asctime();
X  char *ctime();
X  
X  if(argc != 2) usage(*argv) ;
X  
X  if((cp = gdate(*++argv,&tm)) != NULL)
X    printf("error: %s (%s)\n",*argv,cp) ;
X  
X  printf("year : %d month: %d day: %d\n",
X	 tm.tm_year,tm.tm_mon,tm.tm_mday);
X  printf("day of month: %d hour: %d minute: %d second: %d\n",
X	 tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec) ;
X  printf("day of year: %d day of week: %d dst: %d\n",
X	 tm.tm_yday, tm.tm_wday, tm.tm_isdst) ;
X  
X  t = time(NULL) ;
X  t2 = tm_to_time(&tm) ;
X  
X  printf("time_t of now %d, of arg %d\n",t, t2 ) ;
X  printf("Now:  %s", ctime(&t) );
X  printf("Arg:  %s", ctime(&t2) );
X  exit(0) ;
X}
X
X#endif	/* DEBUG */
X
SHAR_EOF
if test 13521 -ne "`wc -c 'gdate.c'`"
then
	echo shar: error transmitting "'gdate.c'" '(should have been 13521 characters)'
fi
echo shar: extracting "'nag.c'" '(29846 characters)'
if test -f 'nag.c'
then
	echo shar: over-writing existing file "'nag.c'"
fi
sed 's/^X//' << \SHAR_EOF > 'nag.c'
X/*----------------------------------------------------------------
X *
X * Nag.c -- annoying reminder service daemon
X *
X * Sun Aug 24 14:18:08 PDT 1986
X *
X * by Dave Brower, {amdahl, cbosgd, mtxinu, sun}!rtech!gonzo!daveb
X *
X * Copyright 1986, David C Brower.  All rights reserved.
X *
X * This is a preliminary version.  The final release will be offered
X * with fewer restrictions.
X *
X * Nag should be launched out of your .login or .profile.  It
X * periodically reads your ~/.nag file and executes commands
X * that can be used as reminders of upcoming events.  The environment
X * variable NAGFILE can be used to get input from something other than
X * the ~/.nag file.
X *
X * NAGFILE FORMAT:
X * ---------------
X *
X * The ~/.nag file should contain lines of the form:
X *
X *    status day time interval command
X *
X * where:
X *
X * status	is one of
X *		   1. '#' indicating a commented out reminder
X *		   2. ':' indicating a silenced reminder
X *		   3. ' ' for an activate reminder.
X *		Other values produce unpredicatable results.
X *
X * day		is one of:
X *		   1.  A date, "8/8/88", "8-Aug-88", etc., but no blanks.
X *		   2.  '*' for any day.
X *		   3.  A day, "Sun", "Mon", ...
X *		The last is presently unimplemented (sorry).
X *
X * time		is a time spec, "8AM", "23:00", etc., but no blanks
X *
X * interval	is a colon separated list of minutes after time at which
X *		   to execute the command, e.g.,
X *
X *			 -30:-15:0:5:10
X *
X *		   produces execution 30 and 15 minutes before the event,
X *		   at the time, and 5 and 10 minutes later.
X *
X * command	is a command to execute with /bin/sh.  Some shell variables
X *		are set for use in messages:
X *
X *			$pretime	-interval
X *			$posttime	interval
X *			$now		hh:mm of the current time
X *			$then		hh:mm of the parent event
X *
X * Blank lines are ignored.
X *
X * Example:
X *
X *	# don't forget to eat.
X *	 * 12:30PM 0 writebig "Lunch Time"
X *
X *	# Weekly warning that has been silenced.
X *      :Mon 3:00PM -30:-20:-10:-5:0 echo "^GStatus report in $time"
X *
X *	# Active Weekly warning.
X *	 Fri 1:30PM -20:-10:-5:0:5:10 echo "^GCommittee meeting in $time"
X *
X *	# One shot warning to call the east coast.
X *	 8/25/86 1:30PM -180:-120:-60:0:10:20 echo "^GCall DEC Marlblerow"
X *
X * NAG
X * ---
X *
X * Nag puts itself in the background, and exits when you logout.
X * Standard output and standard error go to your terminal.
X *
X * Each time it wakes up, it sees if the ~/.nag file has changed.
X * If so, it builds an event queue for lines without '#' comment symbols.
X *
X * Events that were not silenced with 'X' and were due before "now"
X * are executed.  If the event was the last for an entry in ~/.nag,
X * the file is edited to re-enable it from a gagged state.
X *
X * The program then sleeps for at most MINSLEEP minutes.
X *
X * OKOK
X * ----
X *
X * The "okok" program just edits ~/.nag and prepends an 'X' to lines
X * that need to be shut up.
X *
X * BUILD INSTRUCTIONS:
X * -------------------
X *
X *  cc -o nag [ -DSYS5 ] nag.c gdate.c
X *  ln nag okok
X *
X *  The code compiles for a BSD system by default.
X *
X * CAVEATS:
X * --------
X *
X * Sorry Christopher, it probably won't work if stty nostop is set.
X *
X */
X
X# include <stdio.h>
X# include <sys/types.h>
X# include <sys/stat.h>
X# include <signal.h>
X# include <pwd.h>
X# include <ctype.h>
X
X# ifdef SYS5
X#	include		<string.h>
X#	include		<time.h>
X#	define		index		strchr
X#	define		rindex		strrchr
X# else
X#	include		<strings.h>
X#	include		<sys/time.h>
X# endif
X
X/*----------------
X *
X *	defines
X *
X */
X
X# define DPRINTF	if(Debug) (void)fprintf
X
X# define COMCHAR	'#'
X# define SILCHAR	':'
X
X# define HRSECS		3600L
X
X# define CTIMELEN	32	/* length of a date/time string */
X
X# define MINSLEEP	(5*60)
X# define MAXARGS	5120	/* max arg/env size on System V */
X
X# define TRUE		(1)
X# define FALSE		(0)
X
X# define min(a,b)	((a) < (b) ? (a) : (b))
X
X/*----------------
X *
X *	typedefs and structure definitions
X *
X */
X
X/*
X * A NAGLINE is a parsed entry from the .nag file.  We keep
X * a list of them representing the current file, so we can
X * write it back out easily.
X */
X
Xtypedef struct nagline	NAGLINE;
X
Xstruct nagline
X{
X    NAGLINE * next;		/* Next in the chain */
X    int type;			/* COMMENT, SILENT, PENDING, BAD */
X#	define UNKNOWN		0
X#	define COMMENT		1
X#	define SILENT		2
X#	define PENDING		3
X#	define BAD		4
X
X    int errtype;		/* if type is BAD, cause of error */
X#	define NOERR		0
X#	define EMPTY		1
X#	define DATEBAD		2
X#	define NOTIME		3
X#	define TIMEBAD		4
X#	define NOINTERVALS	5
X#	define NOCMD		6
X
X    time_t atime;		/* absolute time of event */
X    char *err;			/* string that caused the error */
X    char *line;			/* the raw line, allocated */
X    char *datestr;		/* the date string, allocated */
X    char *timestr;		/* the time string, allocated */
X    char *intstr;		/* extracted string of intervals, allocated */
X    char *cmd;			/* extracted command to execute, allocated */
X};
X
Xstatic
Xchar *linetypes[] =
X{
X  "Unknown",
X  "Comment",
X  "Silent",
X  "Pending",
X  "Bad"
X};
X
Xstatic
Xchar *parserrs[] =
X{
X  "No error",
X  "Empty line",
X  "Bad date",
X  "No time",
X  "Bad time",
X  "No intervals",
X  "No command"
X};
X
X
X/*
X * An EVENT is an entry in the event queue.
X */
X
Xtypedef struct event	EVENT;
X
Xstruct event
X{
X    EVENT * next;		/* next event in chain */
X    NAGLINE *lp;		/* the parent nagline */
X    time_t etime;		/* absolute time of the event */
X    int offset;			/* minutes difference with parent time */
X};
X
X
X/*----------------
X *
X *	File local variables
X *
X */
X
Xstatic char *Myname="";		/* name from argv[0] */
Xstatic time_t Now = 0;		/* absolute time of "now" */
Xstatic time_t Last = 0;		/* time last time we were awake */
Xstatic NAGLINE *Flist = NULL;	/* lines from the file */
Xstatic NAGLINE *Flast = NULL;	/* last line from the file */
Xstatic EVENT *Evq = NULL;	/* the global event queue */
Xstatic char Origlogin[20] = "";	/* login name when program started */
Xstatic char Nagfile[ 256 ] = ""; /* full path of the nag file */
Xstatic int Debug = FALSE;	/* debugging? */
X
Xstatic char Laststr[ CTIMELEN ]; /* ctime output for last time through */
Xstatic char Nowstr[ CTIMELEN ];	/* ctime output for this time through */
X
X/*----------------
X *
X *	Forward and external function definitions
X *
X */
X
X/* library defined */
X
Xextern char *getlogin();	/* login name in /etc/utmp */
Xextern char *getenv();		/* get an environment variable */
Xextern struct passwd *getpwuid(); /* passwd entry for this user */
Xextern time_t time();
Xextern struct tm *localtime();
Xextern char *fgets();
Xextern char *index();
Xextern char *rindex();
Xextern int sprintf();
Xextern void perror();		/* int on BSD? */
Xextern void qsort();		/* int on BSD? */
Xextern unsigned sleep();
Xextern void free();
Xextern void exit();
Xextern char *ctime();
X
X/* gdate.c defined */
X
Xextern char *gdate();		/* date string to time buf struct */
Xextern char *gtime();		/* time string to time buf struct */
Xextern time_t tm_to_time();	/* time buf to secs past epoch	*/
Xextern char *dow[];		/* days of the week names */
Xextern int find();		/* unambiguous search of string tables */
X
X/* forward function references */
X
X# define forward	extern
X
Xforward void nagfile();
Xforward void setup();
X
Xforward int readf();
Xforward int editf();
Xforward int writef();
X
Xforward int parseline();
Xforward void zaplines();
X
Xforward void buildq();
Xforward void zapq();
Xforward void insq();
Xforward void addevents();
Xforward int timecmp();
Xforward void sortq();
Xforward void runq();
X
Xforward void showlines();
Xforward void dumpline();
X
Xforward void showevents();
Xforward void dumpevent();
X
Xforward char *emalloc();
Xforward char *ecalloc();
Xforward FILE *efopen();
X
Xforward char *nctime();
Xforward char *nhour();
Xforward void delay();
Xforward void lowcase();
X
X/*----------------
X *
X * main() -- Main program.
X *
X * Do one time setup, then go into a loop rebuilding the event queue,
X * executing events in order.  Sleep is done after running the queue.
X *
X */
X/*ARGSUSED*/
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X    char *cp;
X
X    if(argc > 1)
X	Debug = TRUE;
X
X    Myname = (cp = rindex(argv[0], '/')) ? cp + 1 : argv[0] ;
X    nagfile();
X
X    if( !strcmp(Myname, "nag") )
X    {
X	setup();
X
X# ifndef FOREGROUND
X	DPRINTF(stderr, "forking to self-backgrounnd");
X	if(fork())
X	    exit(0);
X# endif
X	/* pretend we started at the epoch */
X	Now = 0;
X	(void) strcpy( Nowstr, nctime( &Now ));
X
X	/*
X	 * This loop never exits.
X	 *
X	 * The program terminates in delay() when the user logs
X	 * off this terminal.
X	 */
X	for(;;)
X	{
X	    (void) strcpy( Laststr, Nowstr );
X	    Last = Now;
X
X	    Now = time(NULL);
X	    (void) strcpy( Nowstr, nctime( &Now ) );
X
X	    DPRINTF(stderr, "\nLoop:\tLast %s\tNow %s\n", Laststr, Nowstr);
X
X	    if ( readf() )
X		buildq();
X
X	    runq();
X	}
X    }
X    else if ( !strcmp(Myname, "okok"))
X    {
X	Now = time( NULL );
X	(void) strcpy( Nowstr, nctime( &Now ));
X
X	if ( readf() )
X	{
X	    buildq();
X	    if ( editf( PENDING ) )
X		exit( writef() );
X	}
X	else
X	{
X	    (void) fprintf(stderr, "%s: Can't read %s\n", Myname, Nagfile );
X	    exit(1);
X	}
X    }
X    else
X    {
X	(void) fprintf(stderr, "Identity crisis: \"%s\" bad program name\n",
X		       argv[0]);
X	exit(1);
X    }
X    exit(0);
X    /*NOTREACHED*/
X}
X
X/*----------------
X *
X * nagfile -- get the full .nag file path
X *
X */
Xvoid
Xnagfile()
X{
X    register char *home;
X    register char *cp;
X
X    /* remember who you are to check for logout later */
X
X    (void) strcpy(Origlogin, getlogin());
X
X    /* expand the Nagfile name */
X
X    if( cp = getenv("NAGFILE") )
X	(void)strcpy( Nagfile, cp );
X    else if( home = getenv("HOME") )
X	(void) sprintf( Nagfile, "%s/.nag", home );
X    else
X    {
X	(void) fprintf(stderr, "%s: HOME is not set\n", Myname );
X	exit(1);
X    }
X
X    DPRINTF(stderr, "Origlogin %s, Nagfile %s\n", Origlogin, Nagfile);
X}
X
X/*----------------
X *
X * setup() -- one time initialization.
X *
X * Setup signals so we don't go away.
X * accidentally.
X *
X */
Xvoid
Xsetup()
X{
X    if(!Debug)
X    {
X	(void) signal( SIGQUIT, SIG_IGN );
X	(void) signal( SIGTERM, SIG_IGN );
X# ifdef SIGTTOU
X	(void) signal( SIGTTOU, SIG_IGN );
X# endif
X    }
X}
X
X
X/*----------------
X *
X * readf() -- read the nagfile and build in memory copy.
X *
X * Returns TRUE if the file was read.
X */
Xint
Xreadf()
X{
X    register NAGLINE *lp;
X    register FILE *fp;
X    char line[ MAXARGS ];
X    struct stat newstat;
X    static struct stat laststat = { 0 };
X    static time_t readtime = 0;
X
X    /* check to see if Nagfile has changed, and reread file. */
X
X    if(stat(Nagfile, &newstat))
X    {
X	/* set it the epoch, but don't complain */
X	newstat.st_mtime = 0;
X    }
X
X    /* if file changed, or we read it more than 12 hours ago */
X
X    if ( newstat.st_mtime <= laststat.st_mtime
X	|| (readtime && Now > 0 && readtime < (Now - (HRSECS * 12))))
X    {
X	DPRINTF(stderr, "already read %s\n", Nagfile );
X	return FALSE;
X    }
X
X    /* rebuild the internal copy of the file */
X
X    DPRINTF(stderr, "reading Nagfile\n");
X
X    laststat = newstat;
X    readtime = Now;
X
X    zaplines();
X
X    /* warn, but don't fatal if file can't be opened this time through */
X
X    if ( NULL==(fp = efopen(Nagfile, "r")))
X	return FALSE;
X
X    /* build the new incore copy */
X
X    while( NULL != fgets( line, sizeof(line), fp ) )
X    {
X	/* Lose trailing newline */
X	line[ strlen(line) - 1 ] = '\0';
X
X	/*ALIGNOK*/
X	lp = (NAGLINE *) ecalloc( sizeof(*lp), 1 );
X
X	if( parseline( line, lp ) )
X	{
X	    if( lp->type == BAD )
X		DPRINTF(stderr, "Parsed OK: %s\n", lp->line );
X	    else
X		DPRINTF(stderr, "Parsed OK: %s %s %s %s\n",
X			lp->datestr,
X			lp->timestr,
X			lp->intstr,
X			lp->cmd );
X	}
X	else
X	{
X	    (void) fprintf(stderr, "%s: Can't parse line:\n%s\n%s %s\n",
X			   Myname,
X			   lp->line,
X			   parserrs[ lp->errtype ],
X			   lp->err );
X	}
X
X	if( !Flist )
X	    Flist = lp;
X	if( Flast )
X	    Flast->next = lp;
X	Flast = lp;
X    }
X    (void) fclose(fp);
X
X    if(Debug)
X    {
X	(void) fprintf(stderr, "Read file OK\n");
X	showlines( "\nLines after file read in:\n" );
X    }
X
X    return TRUE;
X}
X
X
X/*----------------
X *
X * editf() -- interactively edit the nag file in memory, then write it out.
X *
X * Used by 'okok' to make PENDING events SILENT; can also be used to
X * make SILENT events PENDING.
X *
X * Goes WAY out of it's way to force i/o to be on the terminal.
X *
X * Returns TRUE if lines were changed.
X */
Xint
Xeditf( what )
Xregister int what;
X{
X    register FILE *ifp;
X    register FILE *ofp;
X    register NAGLINE *lp;
X    register EVENT *ep;
X    register int changed = FALSE;
X
X    char buf[ 80 ];
X
X    if( ( ifp = efopen( "/dev/tty", "r" ) ) == NULL )
X	return( changed );
X
X    if( ( ofp = efopen( "/dev/tty", "w" ) ) == NULL )
X	return( changed );
X
X    setbuf( ofp, NULL );	/* force output to be unbuffered */
X
X    for( lp = Flist; lp ; lp = lp->next )
X    {
X	if( lp->type == what )
X	{
X	    /* only display events on the queue within 12 hours */
X
X	    for( ep = Evq; ep && ep->lp != lp; ep = ep->next )
X		continue;
X
X	    if( !ep || ep->etime > Now + (HRSECS * 12) )
X		continue;
X
X	    (void) fprintf( ofp, "Silence %s: %s (y/n/q)? ",
X	    		    lp->timestr, lp->cmd ) ;
X
X	    if( fgets( buf, sizeof(buf), ifp ) == NULL )
X		break;
X
X	    if( buf[ 0 ] == 'y' || buf[ 0 ] == 'Y' )
X	    {
X		lp->type = ( what == PENDING ) ? SILENT : PENDING;
X		changed = TRUE;
X	    }
X
X	    /* stop querying if a 'q' is entered */
X
X	    if( buf[ 0 ] == 'q' || buf[ 0 ] == 'Q' )
X	    	break;
X	}
X    }
X    (void) fclose( ifp );
X    (void) fclose( ofp );
X    return ( changed );
X}
X
X
X/*----------------
X *
X * writef() -- Write the file back out after a change.
X *
X * Returns TRUE if file wrote OK.
X */
Xint
Xwritef()
X{
X    char buf[ 80 ];
X
X    register int err;
X    register FILE *fp;
X    register NAGLINE *lp;
X
X    DPRINTF(stderr, "Writing %s\n", Nagfile );
X
X    if( ( fp = efopen( Nagfile, "w" ) ) == NULL )
X	return (FALSE);
X
X    err = 0;
X    for( lp = Flist; lp && err >= 0 ; lp = lp->next )
X    {
X	switch( lp->type )
X	{
X	case BAD:
X	case COMMENT:
X	    err = fprintf( fp, "%s\n", lp->line );
X	    break;
X	default:
X	    err = fprintf( fp, "%c%s %s %s %s\n",
X			  lp->type == SILENT ? SILCHAR : ' ',
X			  lp->datestr,
X			  lp->timestr,
X			  lp->intstr,
X			  lp->cmd );
X	    break;
X	}
X    }
X
X    if( err < 0 )
X    {
X	DPRINTF( stderr, "err %d\n", err );
X	(void) sprintf( buf, "%s: error writing %s", Myname, Nagfile );
X	perror( buf );
X    }
X    else if( (err = fclose( fp ) ) < 0 )
X    {
X	(void) sprintf( buf, "%s: error closing %s", Myname, Nagfile );
X	perror( buf );
X	return( FALSE );
X    }
X    return ( err >= 0 );
X}
X
X
X/*----------------
X *
X * parseline() -- Split text into a NAGLINE more amenable to processing.
X *
X *	Returns TRUE with the NAGLINE all set up if parsed OK.
X *	Returns FALSE with the line->type set to BAD,
X *		      and line->errtype set if undecipherable.
X *
X *
X *	in the code, buf points to the first character not processed,
X *                   cp points to the last character examined.
X *
X *               cp places nulls in likely places.
X *
X *	This is a very ugly function and should be rewritten.
X */
Xint
Xparseline( buf, lp )
Xregister char *buf;
Xregister NAGLINE *lp;
X{
X    register char *cp;
X    register int  today;
X    register int	i;
X    time_t	d;
X    time_t	t;
X    int		anyday;	
X    struct tm ntm;		/* now tm struct */
X    struct tm dtm;		/* date tm struct */
X    struct tm ttm;		/* time tm struct */
X
X    anyday = FALSE;
X    lp->line = strcpy( emalloc( strlen( buf ) + 1 ), buf );
X
X    /*
X     * determine line type, and advance buf to first non-blank after
X     * the status field
X     */
X
X    switch (*buf)
X    {
X    case COMCHAR:
X	lp->type = COMMENT;
X	return TRUE;
X	/*NOTREACHED*/
X
X    case SILCHAR:
X	lp->type = SILENT;
X	buf++;
X	break;
X
X    default:
X	lp->type = PENDING;
X	break;
X    }
X
X    /* skip to non-whitespace */
X
X    while( *buf && isspace(*buf))
X	buf++;
X
X    /* empty line isn't fatal (it's a comment) */
X
X    if (!*buf) {
X	lp->type = BAD;
X	lp->errtype = EMPTY;
X	lp->err = buf;
X	return TRUE;
X    }
X
X    /* bracket the day/date, and null terminate it */
X
X    for( cp = buf; *cp && !isspace( *cp ); cp++ )
X	continue;
X    if( *cp ) *cp++ = '\0';
X    else *cp = '\0';
X
X    /* cp now positioned at char past null, or on null at the end */
X
X    /*
X     * buf points at the day field; figure out the
X     * absolute time of "Midnight" of the right day for the event.
X     */
X    lp->datestr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
X
X    /* figure when midnight of today was */
X
X    ntm = *localtime( &Now );
X    ntm.tm_sec = 0;
X    ntm.tm_min = 0;
X    ntm.tm_hour = 0;
X
X    if (*buf == '*')
X    {
X	anyday = TRUE;
X	dtm = ntm;
X    }
X    else
X    {
X
X	/* parse date */
X
X	if( NULL != gdate( buf, &dtm ) )
X	{
X	    DPRINTF(stderr, "not a date, maybe a day\n");
X
X	    /* maybe it's a day name... */
X
X	    lowcase( buf );
X	    if( (i = find( buf, dow )) >= 0 )
X	    {
X		i--;
X		today = ntm.tm_wday;
X		DPRINTF(stderr, "today %s, event %s\n",
X			dow[ today ],
X			dow[ i ] );
X		if( i < today )
X		    i += 7;	/* it's next week */
X		d = Now + (( i - today ) * HRSECS * 24 );
X		dtm = *localtime( &d );
X		dtm.tm_sec = 0;
X		dtm.tm_min = 0;
X		dtm.tm_hour = 0;
X	    }
X	    else
X	    {
X		DPRINTF(stderr, "find of %s in dow returned %d\n", buf, i );
X		lp->type = BAD;
X		lp->errtype = DATEBAD;
X		lp->err = buf;
X		return FALSE;
X	    }
X	}
X    }
X
X    d = tm_to_time( &dtm );
X    DPRINTF(stderr, "parseline: date %s\n", nctime(&d) );
X
X    /* advance to time */
X
X    for( buf = cp ; *buf && isspace(*buf); buf++)	/* skip blanks */
X	continue;
X
X    if (!*buf) {
X	lp->type = BAD;
X	lp->errtype = NOTIME;
X	lp->err = buf;
X	return FALSE;
X    }
X
X    /* bracket the time */
X
X    for( cp = buf; *cp && !isspace( *cp ); cp++ )
X	continue;
X    if( *cp ) *cp++ = '\0';
X    else *cp = '\0';
X
X    /*
X     * buf now at time field, figure offset until event,
X     * then fill in absolute time.
X     *
X     * gtime can't fail -- it will say it's 00:00 if it
X     * doesn't understand.
X     */
X    DPRINTF(stderr, "parseline: time buf %s\n", buf );
X    lp->timestr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
X    (void) gtime( buf, &ttm );
X    t = (ttm.tm_hour * HRSECS) + (ttm.tm_min * 60);
X    lp->atime = d + t;
X
X    /*
X    ** If past the event, and it's for any day, do it tomorrow. 
X    ** BUG:  This breaks if there is an interval after the event
X    ** This is a rare case, and I haven't yet thought of a clean fix.
X    */
X    if( anyday && lp->atime < Now )
X    	lp->atime += HRSECS * 24;
X
X    DPRINTF(stderr, "parseline: time offset %s is %d seconds, %02d:%02d\n",
X	    buf, t, t / HRSECS, t % HRSECS );
X    DPRINTF(stderr, "parseline: etime %s\n", nctime(&lp->atime));
X
X    /* advance to intervals */
X
X    for( buf = cp; *buf && isspace(*buf); buf++)
X	continue;
X
X    if (!*buf)
X    {
X	lp->type = BAD;
X	lp->errtype = NOINTERVALS;
X	lp->err = buf;
X	return FALSE;
X    }
X
X    /* bracket the intervals */
X
X    for( cp = buf; *cp && !isspace( *cp ); cp++ )
X	continue;
X    if( *cp ) *cp++ = '\0';
X    else *cp = '\0';
X
X    /* save the interval string. */
X
X    lp->intstr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
X
X    /* take rest of the line as the command */
X
X    if (!*cp)
X    {
X	lp->type = BAD;
X	lp->errtype = NOCMD;
X	lp->err = strcpy( emalloc ( strlen( cp ) + 1 ), cp );
X	return FALSE;
X    }
X
X    lp->cmd = strcpy( emalloc ( strlen( cp ) + 1 ), cp );
X
X    return TRUE;
X}
X
X
X/*----------------
X *
X * zaplines() -- delete all NAGLINEs and free their space
X *
X */
Xvoid
Xzaplines()
X{
X    register NAGLINE *lp;
X    register NAGLINE *nlp;
X
X    for( lp = Flist; lp ; lp = nlp )
X    {
X	nlp = lp->next;
X
X	if( lp->line )
X	    free(lp->line);
X	if( lp->datestr )
X	    free(lp->datestr);
X	if( lp->timestr )
X	    free(lp->timestr);
X	if( lp->intstr )
X	    free(lp->intstr);
X	if( lp->cmd )
X	    free(lp->cmd);
X
X	free( lp );
X    }
X    Flast = Flist = NULL;
X}
X
X
X/*----------------
X *
X * buildq() --  Rebuild the event queue if the .nag file has changed.
X *
X */
Xvoid
Xbuildq()
X{
X    register NAGLINE *lp;
X
X    DPRINTF(stderr, "buildq: rebuilding the event queue\n");
X
X    zapq();
X
X    for( lp = Flist; lp; lp = lp->next )
X    {
X	/* add events for silenced lines too. */
X	if( lp->type != COMMENT )
X	    addevents( lp );
X    }
X
X    sortq();
X
X    if(Debug)
X	showevents( "Event queue after rebuild and sort\n" );
X}
X
X
X/*----------------
X *
X * zapq() -- Destroy an event queue, setting the head back to NULL.
X *
X * Only the actual element is freed.
X */
Xvoid
Xzapq()
X{
X    register EVENT *this;
X    register EVENT *next;
X
X    for ( this = Evq; this ; this = next )
X    {
X	next = this->next;
X	free( this );
X    }
X    Evq = NULL;
X}
X
X/*----------------
X *
X * insq() -- Add a new EVENT to the head of a queue.
X *
X */
Xvoid
Xinsq( etime, offset, lp )
Xtime_t etime;
Xregister int offset;
XNAGLINE *lp;
X{
X    register EVENT *ep;
X
X    etime += (offset * 60);
X
X    /* add events after last time we ran, but no more than 24 hours
X       in the future */
X
X    if( ( etime >= Now || ( Last && etime > Last ) )
X       && etime < ( Now + ( HRSECS * 24 ) ) )
X    {
X	DPRINTF(stderr, "insq: Adding %s at %s\n", lp->cmd, nctime(&etime) );
X    }
X    else			/* too late */
X    {
X	DPRINTF(stderr, "insq: Dropping %s at %s\n", lp->cmd, nctime(&etime) );
X	return;
X    }
X
X    /*ALIGNOK*/
X    ep = (EVENT *) emalloc( sizeof(*ep) );
X    ep->etime = etime;
X    ep->offset = offset;
X    ep->lp = lp;
X
X    /* splice into the head of the queue */
X    ep->next = Evq;		/* NULL, if last event */
X    Evq = ep;
X}
X
X
X/*----------------
X *
X * addevents() -- Add pending events for the NAGLINE to the queue.
X *
X * Events in the past are not considered.
X * If the command has been silenced, don't do the command.
X *
X */
Xvoid
Xaddevents( lp )
Xregister NAGLINE *lp;
X{
X    register char *cp;		/* ptr into the interval string */
X    int offset;			/* offset in minutes */
X
X    /* for every numeric value in the interval string... */
X
X    for( cp = lp->intstr; cp && *cp ; cp = index( cp, ':' ) )
X    {
X	if (*cp == ':')		/* skip past optional ':' */
X	    cp++;
X	if (!*cp)		/* ignore trailing ':' */
X	    return;
X
X	/* read (possibly) signed interval value */
X
X	if( 1 != sscanf( cp, "%d", &offset ) )
X	{
X	    (void) fprintf(stderr, "%s: bad intervals '%s'\n", Myname,
X			   lp->intstr );
X	    return;
X	}
X	insq( lp->atime, offset, lp );
X    }
X}
X
X
X
X/*----------------
X *
X * timecmp() -- Compare time of two events.
X *
X * Made slightly tricky since it must return an int, not a time_t.
X *
X */
Xint
Xtimecmp( a, b )
Xregister EVENT **a;
Xregister EVENT **b;
X{
X    time_t val = (*a)->etime - (*b)->etime;
X
X    return( val < 0 ? -1 : val > 0 );
X}
X
X
X/*----------------
X *
X * sortq() -- Sort the event queue into chronological order.
X *
X * 1. Create an array of pointers to the events in the queue.
X * 2. Sort the array by time of the pointed-to events.
X * 3. Rebuild the queue in the order of the array.
X *
X */
Xvoid
Xsortq()
X{
X    register unsigned int n;	/* number of events in the queue */
X    register unsigned int i;	/* handy counter */
X    register EVENT **events;	/* allocated array of EVENT ptrs */
X    register EVENT **ap;	/* ptr into allocated events */
X    register EVENT *ep;		/* pointer in event chain */
X
X    forward int timecmp();
X
X    n = 0;
X    for( ep = Evq; ep; ep = ep->next )
X	n++;
X
X    DPRINTF(stderr, "sortq:  %d events\n", n );
X
X    if ( n < 2 )
X	return;
X
X    /* build array of ptrs to events */
X
X    /*ALIGNOK*/
X    ap = events = (EVENT **) ecalloc( (unsigned)sizeof(**ap), n );
X
X    /* build array of ptrs to events */
X    for( ep = Evq; ep; ep = ep->next )
X	*ap++ = ep;
X
X    /* sort by ascending time */
X    (void) qsort( events, (unsigned)n, sizeof(*events), timecmp );
X
X    /* rechain the event queue from the sorted array */
X    Evq = ep = events[0];
X    for ( i = 0 ; i < n ; )
X    {
X	ep->next = events[i++];
X	ep = ep->next;
X    }
X    ep->next = NULL;
X
X    free( events );
X}
X
X
X/*----------------
X *
X * runq() -- Execute all events that are due.
X *
X * Sleep until the next scheduled event.  If there are none, or
X * next is far away, sleep for MINSLEEP and try again.
X *
X */
Xvoid
Xrunq()
X{
X    char cmd[ 5120 ];
X    char now[ CTIMELEN ];
X    register EVENT *evq;	/* standin for global Evq in loop */
X    register EVENT *ep;		/* next event */
X    register NAGLINE *lp;
X    int dsecs;
X
X    DPRINTF(stderr, "runq start at %s\n", Nowstr );
X
X    evq = Evq;			/* fast access, be sure to save back */
X
X    /*
X     * Execute commands that are due.
X     *
X     * Keeps head of the queue current by cutting out events as
X     * they are processed.
X     *
X     * The loop breaks out when the queue is gobbled up,
X     * or we get to an event that is not due now.
X     */
X
X    while( evq && evq->etime <= Now )
X    {
X	lp = evq->lp;
X
X	DPRINTF(stderr, "due at %s:\n", nctime( &evq->etime ) );
X
X	/* Run a PENDING event */
X
X	if( lp->type == PENDING && lp->cmd )
X	{
X	    (void)strcpy( now, &Nowstr[ 11 ] );
X	    now[ 5 ] = '\0';
X
X	    (void)sprintf( cmd, "pretime=%d;posttime=%d;now=%s;then=%s;%s\n",
X			  -evq->offset,
X			  evq->offset,
X			  now,
X			  nhour( &lp->atime ),
X			  lp->cmd );
X
X	    DPRINTF(stderr, "executing:\n%s\n", cmd );
X	    if( system( cmd ) )
X		(void) fprintf( stderr, "%s: Trouble running\n'%s'\n",
X			       Myname, cmd );
X	}
X
X	/* if it's a SILENT event, is it time to make it PENDING? */
X
X	if( lp->type == SILENT )
X	{
X	    /* find the queue end or the next event for the line */
X
X	    for( ep = evq->next ; ep && ep->lp != lp ; ep = ep->next )
X		continue;
X
X	    /* if match, or it was the last in the queue, turn it on */
X
X	    if ( ep )
X	    {
X		DPRINTF(stderr, "SILENT event\n");
X	    }
X	    else
X	    {
X		DPRINTF(stderr, "Last SILENT event, making PENDING again.\n");
X		lp->type = PENDING;
X
X		/*
X		 * if the write fails, keep going and hope the user fixes
X		 * the nag file.  If we exit, the daemon would need
X		 * to be restarted by hand.  Since it won't do anything
X		 * but sleep and exit when the user logs off, no harm
X		 * is done by sticking around.
X		 */
X		(void) writef();
X	    }
X	}
X	ep = evq->next;
X	free( evq );
X	evq = ep;
X    }				/* for events on the queue */
X
X    dsecs = evq ? min( evq->etime - Now, MINSLEEP) : MINSLEEP;
X
X    DPRINTF(stderr, "sleeping for %d seconds, next %s\n",
X	    dsecs,
X	    evq ? nctime( &evq->etime ) : "never" );
X
X    Evq = evq;			/* back to global var */
X
X    delay( dsecs );
X}
X
X
X/*----------------
X *
X * emalloc() -- malloc with error msg.
X *
X */
Xchar *
Xemalloc( size )
Xregister int size;
X{
X    register char *ptr;
X    extern char *malloc();
X
X    if ( ( ptr = malloc( (unsigned) size ) ) == NULL )
X    {
X	(void) fprintf(stderr, "%s: Can't malloc %d bytes\n", Myname, size );
X	exit(1);
X    }
X    return( ptr );
X}
X
X/*----------------
X *
X * ecalloc() -- calloc with error message.
X *
X */
Xchar *
Xecalloc( n, size )
Xregister unsigned int n;
Xregister unsigned int size;
X{
X    register char *ptr;
X    extern char *calloc();
X
X    if ( ( ptr = calloc( (unsigned) size, n ) ) == NULL )
X    {
X	(void) fprintf(stderr, "%s: Can't calloc %d bytes\n", Myname, size * n);
X	exit(1);
X    }
X    return( ptr );
X}
X
X/*
X * efopen()  -- fopen with error message on failure (no fatal error)
X */
XFILE *
Xefopen( file, mode )
Xchar *file;
Xchar *mode;
X{
X    char buf [ 80 ];
X    register FILE * fp;
X
X    if( (fp = fopen( file, mode )) == NULL )
X    {
X	(void)sprintf( buf, "%s: can't open file %s with mode \"%s\"",
X		      Myname, file, mode );
X	perror( buf );
X    }
X    return( fp );
X}
X
X
X/*
X * showline() -- Dump the line list.
X */
Xvoid
Xshowlines( msg )
Xchar *msg;
X{
X    register NAGLINE *lp;
X
X    (void) fprintf(stderr, "%s", msg );
X    for( lp = Flist; lp ; lp = lp->next )
X	dumpline( lp );
X}
X
X/*
X * dumpline() -- dump a NAGLINE for debugging.
X */
Xvoid
Xdumpline( lp )
Xregister NAGLINE *lp;
X{
X    if( lp == NULL )
X    {
X	(void) fprintf(stderr, "dumpline: NULL lp\n");
X	return;
X    }
X    (void) fprintf(stderr, "\nline (%s):\n%s\n", linetypes[ lp->type ],
X		   lp->line );
X    switch( lp->type )
X    {
X    case BAD:
X	(void) fprintf(stderr, "%s %s\n", parserrs[ lp->errtype ], lp->err );
X	break;
X
X    case PENDING:
X    case SILENT:
X	(void) fprintf(stderr, "The event is at %s\n", nctime( &lp->atime ));
X    }
X}
X
X/*
X * showevents() -- dump the event list, for debugging.
X */
Xvoid
Xshowevents( msg )
Xchar *msg;
X{
X    register EVENT *ep;
X
X    (void) fprintf(stderr, "%s", msg );
X    for( ep = Evq; ep; ep = ep->next )
X	dumpevent( ep );
X}
X
X/*
X * dumpevent() -- print an event, for debugging.
X */
Xvoid
Xdumpevent( ep )
Xregister EVENT *ep;
X{
X    if( ep == NULL )
X	(void) fprintf(stderr, "dumpevent: NULL ep\n");
X    else
X	(void) fprintf(stderr, "event 0x%x, next 0x%x offset %d time %s\n",
X		       ep, ep->next, ep->offset, nctime(&ep->etime) );
X}
X
X/*
X * nctime() -- ctime with trailing '\n' whacked off.
X */
Xchar *
Xnctime( t )
Xtime_t *t;
X{
X    register char *cp;
X
X    cp = ctime( t );
X    cp[ strlen( cp ) - 1 ] = '\0';
X    return ( cp );
X}
X
X/*
X * nhour() -- return an hh:mm string given a pointer to a time_t.
X */
Xchar *
Xnhour( t )
Xtime_t *t;
X{
X    register char *buf = ctime( t );
X
X    /*
X     * 012345678901234567890123
X     * Wed Dec 31 16:00:00 1969
X     */
X
X    buf[ 16 ] = '\0';
X    return ( &buf[ 11 ] );
X}
X
X
X/*----------------
X *
X * delay() -- like sleep but knows what 0 means.
X *
X * If user logs out, notices and exit with OK status.
X *
X */
Xvoid
Xdelay( secs )
Xint secs;
X{
X    char thislogin[20];
X
X    if( secs > 0)
X    {
X	(void) sleep( (unsigned) secs );
X	(void) strcpy(thislogin, getlogin());
X	if ( strcmp(Origlogin, thislogin) )
X	    exit(0);
X    }
X}
X
X/*
X * lowcase() -- make a string all lower case.
X */
Xvoid
Xlowcase( s )
Xchar *s;
X{
X    while ( *s )
X    {
X	if( isupper( *s ) )
X	    *s = tolower( *s );
X	s++;
X    }
X}
X
X# if 0
X
X/*
X * dumptm() -- show contents of a tm structure.
X */
Xdumptm( tm )
Xstruct tm *tm;
X{
X    (void) fprintf(stderr, "year : %d month: %d day: %d\n",
X		   tm->tm_year,tm->tm_mon,tm->tm_mday);
X    (void) fprintf(stderr, "day of month: %d hour: %d minute: %d second: %d\n",
X		   tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec) ;
X    (void) fprintf(stderr, "day of year: %d day of week: %d dst: %d\n",
X		   tm->tm_yday, tm->tm_wday, tm->tm_isdst) ;
X}
X
X# endif
X
X/* end of nag.c */
X
X
SHAR_EOF
if test 29846 -ne "`wc -c 'nag.c'`"
then
	echo shar: error transmitting "'nag.c'" '(should have been 29846 characters)'
fi
#	End of shell archive
exit 0