Subject: v07i063: Nag reminder service, Part02/02 Newsgroups: mod.sources Approved: mirror!rs 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 X#include X#include X X#ifdef SYS5 X X# define time_t long /* SV is inconsistent, so go with lcd */ X X# include X# include X# include X X# else /* BSD */ X X# include X# include X# include 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 X# include X# include X# include X# include X# include X X# ifdef SYS5 X# include X# include X# define index strchr X# define rindex strrchr X# else X# include X# include 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