home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume7 / nag / part02 / nag.c < prev   
Encoding:
C/C++ Source or Header  |  1986-11-30  |  29.1 KB  |  1,451 lines

  1. /*----------------------------------------------------------------
  2.  *
  3.  * Nag.c -- annoying reminder service daemon
  4.  *
  5.  * Sun Aug 24 14:18:08 PDT 1986
  6.  *
  7.  * by Dave Brower, {amdahl, cbosgd, mtxinu, sun}!rtech!gonzo!daveb
  8.  *
  9.  * Copyright 1986, David C Brower.  All rights reserved.
  10.  *
  11.  * This is a preliminary version.  The final release will be offered
  12.  * with fewer restrictions.
  13.  *
  14.  * Nag should be launched out of your .login or .profile.  It
  15.  * periodically reads your ~/.nag file and executes commands
  16.  * that can be used as reminders of upcoming events.  The environment
  17.  * variable NAGFILE can be used to get input from something other than
  18.  * the ~/.nag file.
  19.  *
  20.  * NAGFILE FORMAT:
  21.  * ---------------
  22.  *
  23.  * The ~/.nag file should contain lines of the form:
  24.  *
  25.  *    status day time interval command
  26.  *
  27.  * where:
  28.  *
  29.  * status    is one of
  30.  *           1. '#' indicating a commented out reminder
  31.  *           2. ':' indicating a silenced reminder
  32.  *           3. ' ' for an activate reminder.
  33.  *        Other values produce unpredicatable results.
  34.  *
  35.  * day        is one of:
  36.  *           1.  A date, "8/8/88", "8-Aug-88", etc., but no blanks.
  37.  *           2.  '*' for any day.
  38.  *           3.  A day, "Sun", "Mon", ...
  39.  *        The last is presently unimplemented (sorry).
  40.  *
  41.  * time        is a time spec, "8AM", "23:00", etc., but no blanks
  42.  *
  43.  * interval    is a colon separated list of minutes after time at which
  44.  *           to execute the command, e.g.,
  45.  *
  46.  *             -30:-15:0:5:10
  47.  *
  48.  *           produces execution 30 and 15 minutes before the event,
  49.  *           at the time, and 5 and 10 minutes later.
  50.  *
  51.  * command    is a command to execute with /bin/sh.  Some shell variables
  52.  *        are set for use in messages:
  53.  *
  54.  *            $pretime    -interval
  55.  *            $posttime    interval
  56.  *            $now        hh:mm of the current time
  57.  *            $then        hh:mm of the parent event
  58.  *
  59.  * Blank lines are ignored.
  60.  *
  61.  * Example:
  62.  *
  63.  *    # don't forget to eat.
  64.  *     * 12:30PM 0 writebig "Lunch Time"
  65.  *
  66.  *    # Weekly warning that has been silenced.
  67.  *      :Mon 3:00PM -30:-20:-10:-5:0 echo "^GStatus report in $time"
  68.  *
  69.  *    # Active Weekly warning.
  70.  *     Fri 1:30PM -20:-10:-5:0:5:10 echo "^GCommittee meeting in $time"
  71.  *
  72.  *    # One shot warning to call the east coast.
  73.  *     8/25/86 1:30PM -180:-120:-60:0:10:20 echo "^GCall DEC Marlblerow"
  74.  *
  75.  * NAG
  76.  * ---
  77.  *
  78.  * Nag puts itself in the background, and exits when you logout.
  79.  * Standard output and standard error go to your terminal.
  80.  *
  81.  * Each time it wakes up, it sees if the ~/.nag file has changed.
  82.  * If so, it builds an event queue for lines without '#' comment symbols.
  83.  *
  84.  * Events that were not silenced with 'X' and were due before "now"
  85.  * are executed.  If the event was the last for an entry in ~/.nag,
  86.  * the file is edited to re-enable it from a gagged state.
  87.  *
  88.  * The program then sleeps for at most MINSLEEP minutes.
  89.  *
  90.  * OKOK
  91.  * ----
  92.  *
  93.  * The "okok" program just edits ~/.nag and prepends an 'X' to lines
  94.  * that need to be shut up.
  95.  *
  96.  * BUILD INSTRUCTIONS:
  97.  * -------------------
  98.  *
  99.  *  cc -o nag [ -DSYS5 ] nag.c gdate.c
  100.  *  ln nag okok
  101.  *
  102.  *  The code compiles for a BSD system by default.
  103.  *
  104.  * CAVEATS:
  105.  * --------
  106.  *
  107.  * Sorry Christopher, it probably won't work if stty nostop is set.
  108.  *
  109.  */
  110.  
  111. # include <stdio.h>
  112. # include <sys/types.h>
  113. # include <sys/stat.h>
  114. # include <signal.h>
  115. # include <pwd.h>
  116. # include <ctype.h>
  117.  
  118. # ifdef SYS5
  119. #    include        <string.h>
  120. #    include        <time.h>
  121. #    define        index        strchr
  122. #    define        rindex        strrchr
  123. # else
  124. #    include        <strings.h>
  125. #    include        <sys/time.h>
  126. # endif
  127.  
  128. /*----------------
  129.  *
  130.  *    defines
  131.  *
  132.  */
  133.  
  134. # define DPRINTF    if(Debug) (void)fprintf
  135.  
  136. # define COMCHAR    '#'
  137. # define SILCHAR    ':'
  138.  
  139. # define HRSECS        3600L
  140.  
  141. # define CTIMELEN    32    /* length of a date/time string */
  142.  
  143. # define MINSLEEP    (5*60)
  144. # define MAXARGS    5120    /* max arg/env size on System V */
  145.  
  146. # define TRUE        (1)
  147. # define FALSE        (0)
  148.  
  149. # define min(a,b)    ((a) < (b) ? (a) : (b))
  150.  
  151. /*----------------
  152.  *
  153.  *    typedefs and structure definitions
  154.  *
  155.  */
  156.  
  157. /*
  158.  * A NAGLINE is a parsed entry from the .nag file.  We keep
  159.  * a list of them representing the current file, so we can
  160.  * write it back out easily.
  161.  */
  162.  
  163. typedef struct nagline    NAGLINE;
  164.  
  165. struct nagline
  166. {
  167.     NAGLINE * next;        /* Next in the chain */
  168.     int type;            /* COMMENT, SILENT, PENDING, BAD */
  169. #    define UNKNOWN        0
  170. #    define COMMENT        1
  171. #    define SILENT        2
  172. #    define PENDING        3
  173. #    define BAD        4
  174.  
  175.     int errtype;        /* if type is BAD, cause of error */
  176. #    define NOERR        0
  177. #    define EMPTY        1
  178. #    define DATEBAD        2
  179. #    define NOTIME        3
  180. #    define TIMEBAD        4
  181. #    define NOINTERVALS    5
  182. #    define NOCMD        6
  183.  
  184.     time_t atime;        /* absolute time of event */
  185.     char *err;            /* string that caused the error */
  186.     char *line;            /* the raw line, allocated */
  187.     char *datestr;        /* the date string, allocated */
  188.     char *timestr;        /* the time string, allocated */
  189.     char *intstr;        /* extracted string of intervals, allocated */
  190.     char *cmd;            /* extracted command to execute, allocated */
  191. };
  192.  
  193. static
  194. char *linetypes[] =
  195. {
  196.   "Unknown",
  197.   "Comment",
  198.   "Silent",
  199.   "Pending",
  200.   "Bad"
  201. };
  202.  
  203. static
  204. char *parserrs[] =
  205. {
  206.   "No error",
  207.   "Empty line",
  208.   "Bad date",
  209.   "No time",
  210.   "Bad time",
  211.   "No intervals",
  212.   "No command"
  213. };
  214.  
  215.  
  216. /*
  217.  * An EVENT is an entry in the event queue.
  218.  */
  219.  
  220. typedef struct event    EVENT;
  221.  
  222. struct event
  223. {
  224.     EVENT * next;        /* next event in chain */
  225.     NAGLINE *lp;        /* the parent nagline */
  226.     time_t etime;        /* absolute time of the event */
  227.     int offset;            /* minutes difference with parent time */
  228. };
  229.  
  230.  
  231. /*----------------
  232.  *
  233.  *    File local variables
  234.  *
  235.  */
  236.  
  237. static char *Myname="";        /* name from argv[0] */
  238. static time_t Now = 0;        /* absolute time of "now" */
  239. static time_t Last = 0;        /* time last time we were awake */
  240. static NAGLINE *Flist = NULL;    /* lines from the file */
  241. static NAGLINE *Flast = NULL;    /* last line from the file */
  242. static EVENT *Evq = NULL;    /* the global event queue */
  243. static char Origlogin[20] = "";    /* login name when program started */
  244. static char Nagfile[ 256 ] = ""; /* full path of the nag file */
  245. static int Debug = FALSE;    /* debugging? */
  246.  
  247. static char Laststr[ CTIMELEN ]; /* ctime output for last time through */
  248. static char Nowstr[ CTIMELEN ];    /* ctime output for this time through */
  249.  
  250. /*----------------
  251.  *
  252.  *    Forward and external function definitions
  253.  *
  254.  */
  255.  
  256. /* library defined */
  257.  
  258. extern char *getlogin();    /* login name in /etc/utmp */
  259. extern char *getenv();        /* get an environment variable */
  260. extern struct passwd *getpwuid(); /* passwd entry for this user */
  261. extern time_t time();
  262. extern struct tm *localtime();
  263. extern char *fgets();
  264. extern char *index();
  265. extern char *rindex();
  266. extern int sprintf();
  267. extern void perror();        /* int on BSD? */
  268. extern void qsort();        /* int on BSD? */
  269. extern unsigned sleep();
  270. extern void free();
  271. extern void exit();
  272. extern char *ctime();
  273.  
  274. /* gdate.c defined */
  275.  
  276. extern char *gdate();        /* date string to time buf struct */
  277. extern char *gtime();        /* time string to time buf struct */
  278. extern time_t tm_to_time();    /* time buf to secs past epoch    */
  279. extern char *dow[];        /* days of the week names */
  280. extern int find();        /* unambiguous search of string tables */
  281.  
  282. /* forward function references */
  283.  
  284. # define forward    extern
  285.  
  286. forward void nagfile();
  287. forward void setup();
  288.  
  289. forward int readf();
  290. forward int editf();
  291. forward int writef();
  292.  
  293. forward int parseline();
  294. forward void zaplines();
  295.  
  296. forward void buildq();
  297. forward void zapq();
  298. forward void insq();
  299. forward void addevents();
  300. forward int timecmp();
  301. forward void sortq();
  302. forward void runq();
  303.  
  304. forward void showlines();
  305. forward void dumpline();
  306.  
  307. forward void showevents();
  308. forward void dumpevent();
  309.  
  310. forward char *emalloc();
  311. forward char *ecalloc();
  312. forward FILE *efopen();
  313.  
  314. forward char *nctime();
  315. forward char *nhour();
  316. forward void delay();
  317. forward void lowcase();
  318.  
  319. /*----------------
  320.  *
  321.  * main() -- Main program.
  322.  *
  323.  * Do one time setup, then go into a loop rebuilding the event queue,
  324.  * executing events in order.  Sleep is done after running the queue.
  325.  *
  326.  */
  327. /*ARGSUSED*/
  328. main(argc, argv)
  329. int argc;
  330. char **argv;
  331. {
  332.     char *cp;
  333.  
  334.     if(argc > 1)
  335.     Debug = TRUE;
  336.  
  337.     Myname = (cp = rindex(argv[0], '/')) ? cp + 1 : argv[0] ;
  338.     nagfile();
  339.  
  340.     if( !strcmp(Myname, "nag") )
  341.     {
  342.     setup();
  343.  
  344. # ifndef FOREGROUND
  345.     DPRINTF(stderr, "forking to self-backgrounnd");
  346.     if(fork())
  347.         exit(0);
  348. # endif
  349.     /* pretend we started at the epoch */
  350.     Now = 0;
  351.     (void) strcpy( Nowstr, nctime( &Now ));
  352.  
  353.     /*
  354.      * This loop never exits.
  355.      *
  356.      * The program terminates in delay() when the user logs
  357.      * off this terminal.
  358.      */
  359.     for(;;)
  360.     {
  361.         (void) strcpy( Laststr, Nowstr );
  362.         Last = Now;
  363.  
  364.         Now = time(NULL);
  365.         (void) strcpy( Nowstr, nctime( &Now ) );
  366.  
  367.         DPRINTF(stderr, "\nLoop:\tLast %s\tNow %s\n", Laststr, Nowstr);
  368.  
  369.         if ( readf() )
  370.         buildq();
  371.  
  372.         runq();
  373.     }
  374.     }
  375.     else if ( !strcmp(Myname, "okok"))
  376.     {
  377.     Now = time( NULL );
  378.     (void) strcpy( Nowstr, nctime( &Now ));
  379.  
  380.     if ( readf() )
  381.     {
  382.         buildq();
  383.         if ( editf( PENDING ) )
  384.         exit( writef() );
  385.     }
  386.     else
  387.     {
  388.         (void) fprintf(stderr, "%s: Can't read %s\n", Myname, Nagfile );
  389.         exit(1);
  390.     }
  391.     }
  392.     else
  393.     {
  394.     (void) fprintf(stderr, "Identity crisis: \"%s\" bad program name\n",
  395.                argv[0]);
  396.     exit(1);
  397.     }
  398.     exit(0);
  399.     /*NOTREACHED*/
  400. }
  401.  
  402. /*----------------
  403.  *
  404.  * nagfile -- get the full .nag file path
  405.  *
  406.  */
  407. void
  408. nagfile()
  409. {
  410.     register char *home;
  411.     register char *cp;
  412.  
  413.     /* remember who you are to check for logout later */
  414.  
  415.     (void) strcpy(Origlogin, getlogin());
  416.  
  417.     /* expand the Nagfile name */
  418.  
  419.     if( cp = getenv("NAGFILE") )
  420.     (void)strcpy( Nagfile, cp );
  421.     else if( home = getenv("HOME") )
  422.     (void) sprintf( Nagfile, "%s/.nag", home );
  423.     else
  424.     {
  425.     (void) fprintf(stderr, "%s: HOME is not set\n", Myname );
  426.     exit(1);
  427.     }
  428.  
  429.     DPRINTF(stderr, "Origlogin %s, Nagfile %s\n", Origlogin, Nagfile);
  430. }
  431.  
  432. /*----------------
  433.  *
  434.  * setup() -- one time initialization.
  435.  *
  436.  * Setup signals so we don't go away.
  437.  * accidentally.
  438.  *
  439.  */
  440. void
  441. setup()
  442. {
  443.     if(!Debug)
  444.     {
  445.     (void) signal( SIGQUIT, SIG_IGN );
  446.     (void) signal( SIGTERM, SIG_IGN );
  447. # ifdef SIGTTOU
  448.     (void) signal( SIGTTOU, SIG_IGN );
  449. # endif
  450.     }
  451. }
  452.  
  453.  
  454. /*----------------
  455.  *
  456.  * readf() -- read the nagfile and build in memory copy.
  457.  *
  458.  * Returns TRUE if the file was read.
  459.  */
  460. int
  461. readf()
  462. {
  463.     register NAGLINE *lp;
  464.     register FILE *fp;
  465.     char line[ MAXARGS ];
  466.     struct stat newstat;
  467.     static struct stat laststat = { 0 };
  468.     static time_t readtime = 0;
  469.  
  470.     /* check to see if Nagfile has changed, and reread file. */
  471.  
  472.     if(stat(Nagfile, &newstat))
  473.     {
  474.     /* set it the epoch, but don't complain */
  475.     newstat.st_mtime = 0;
  476.     }
  477.  
  478.     /* if file changed, or we read it more than 12 hours ago */
  479.  
  480.     if ( newstat.st_mtime <= laststat.st_mtime
  481.     || (readtime && Now > 0 && readtime < (Now - (HRSECS * 12))))
  482.     {
  483.     DPRINTF(stderr, "already read %s\n", Nagfile );
  484.     return FALSE;
  485.     }
  486.  
  487.     /* rebuild the internal copy of the file */
  488.  
  489.     DPRINTF(stderr, "reading Nagfile\n");
  490.  
  491.     laststat = newstat;
  492.     readtime = Now;
  493.  
  494.     zaplines();
  495.  
  496.     /* warn, but don't fatal if file can't be opened this time through */
  497.  
  498.     if ( NULL==(fp = efopen(Nagfile, "r")))
  499.     return FALSE;
  500.  
  501.     /* build the new incore copy */
  502.  
  503.     while( NULL != fgets( line, sizeof(line), fp ) )
  504.     {
  505.     /* Lose trailing newline */
  506.     line[ strlen(line) - 1 ] = '\0';
  507.  
  508.     /*ALIGNOK*/
  509.     lp = (NAGLINE *) ecalloc( sizeof(*lp), 1 );
  510.  
  511.     if( parseline( line, lp ) )
  512.     {
  513.         if( lp->type == BAD )
  514.         DPRINTF(stderr, "Parsed OK: %s\n", lp->line );
  515.         else
  516.         DPRINTF(stderr, "Parsed OK: %s %s %s %s\n",
  517.             lp->datestr,
  518.             lp->timestr,
  519.             lp->intstr,
  520.             lp->cmd );
  521.     }
  522.     else
  523.     {
  524.         (void) fprintf(stderr, "%s: Can't parse line:\n%s\n%s %s\n",
  525.                Myname,
  526.                lp->line,
  527.                parserrs[ lp->errtype ],
  528.                lp->err );
  529.     }
  530.  
  531.     if( !Flist )
  532.         Flist = lp;
  533.     if( Flast )
  534.         Flast->next = lp;
  535.     Flast = lp;
  536.     }
  537.     (void) fclose(fp);
  538.  
  539.     if(Debug)
  540.     {
  541.     (void) fprintf(stderr, "Read file OK\n");
  542.     showlines( "\nLines after file read in:\n" );
  543.     }
  544.  
  545.     return TRUE;
  546. }
  547.  
  548.  
  549. /*----------------
  550.  *
  551.  * editf() -- interactively edit the nag file in memory, then write it out.
  552.  *
  553.  * Used by 'okok' to make PENDING events SILENT; can also be used to
  554.  * make SILENT events PENDING.
  555.  *
  556.  * Goes WAY out of it's way to force i/o to be on the terminal.
  557.  *
  558.  * Returns TRUE if lines were changed.
  559.  */
  560. int
  561. editf( what )
  562. register int what;
  563. {
  564.     register FILE *ifp;
  565.     register FILE *ofp;
  566.     register NAGLINE *lp;
  567.     register EVENT *ep;
  568.     register int changed = FALSE;
  569.  
  570.     char buf[ 80 ];
  571.  
  572.     if( ( ifp = efopen( "/dev/tty", "r" ) ) == NULL )
  573.     return( changed );
  574.  
  575.     if( ( ofp = efopen( "/dev/tty", "w" ) ) == NULL )
  576.     return( changed );
  577.  
  578.     setbuf( ofp, NULL );    /* force output to be unbuffered */
  579.  
  580.     for( lp = Flist; lp ; lp = lp->next )
  581.     {
  582.     if( lp->type == what )
  583.     {
  584.         /* only display events on the queue within 12 hours */
  585.  
  586.         for( ep = Evq; ep && ep->lp != lp; ep = ep->next )
  587.         continue;
  588.  
  589.         if( !ep || ep->etime > Now + (HRSECS * 12) )
  590.         continue;
  591.  
  592.         (void) fprintf( ofp, "Silence %s: %s (y/n/q)? ",
  593.                     lp->timestr, lp->cmd ) ;
  594.  
  595.         if( fgets( buf, sizeof(buf), ifp ) == NULL )
  596.         break;
  597.  
  598.         if( buf[ 0 ] == 'y' || buf[ 0 ] == 'Y' )
  599.         {
  600.         lp->type = ( what == PENDING ) ? SILENT : PENDING;
  601.         changed = TRUE;
  602.         }
  603.  
  604.         /* stop querying if a 'q' is entered */
  605.  
  606.         if( buf[ 0 ] == 'q' || buf[ 0 ] == 'Q' )
  607.             break;
  608.     }
  609.     }
  610.     (void) fclose( ifp );
  611.     (void) fclose( ofp );
  612.     return ( changed );
  613. }
  614.  
  615.  
  616. /*----------------
  617.  *
  618.  * writef() -- Write the file back out after a change.
  619.  *
  620.  * Returns TRUE if file wrote OK.
  621.  */
  622. int
  623. writef()
  624. {
  625.     char buf[ 80 ];
  626.  
  627.     register int err;
  628.     register FILE *fp;
  629.     register NAGLINE *lp;
  630.  
  631.     DPRINTF(stderr, "Writing %s\n", Nagfile );
  632.  
  633.     if( ( fp = efopen( Nagfile, "w" ) ) == NULL )
  634.     return (FALSE);
  635.  
  636.     err = 0;
  637.     for( lp = Flist; lp && err >= 0 ; lp = lp->next )
  638.     {
  639.     switch( lp->type )
  640.     {
  641.     case BAD:
  642.     case COMMENT:
  643.         err = fprintf( fp, "%s\n", lp->line );
  644.         break;
  645.     default:
  646.         err = fprintf( fp, "%c%s %s %s %s\n",
  647.               lp->type == SILENT ? SILCHAR : ' ',
  648.               lp->datestr,
  649.               lp->timestr,
  650.               lp->intstr,
  651.               lp->cmd );
  652.         break;
  653.     }
  654.     }
  655.  
  656.     if( err < 0 )
  657.     {
  658.     DPRINTF( stderr, "err %d\n", err );
  659.     (void) sprintf( buf, "%s: error writing %s", Myname, Nagfile );
  660.     perror( buf );
  661.     }
  662.     else if( (err = fclose( fp ) ) < 0 )
  663.     {
  664.     (void) sprintf( buf, "%s: error closing %s", Myname, Nagfile );
  665.     perror( buf );
  666.     return( FALSE );
  667.     }
  668.     return ( err >= 0 );
  669. }
  670.  
  671.  
  672. /*----------------
  673.  *
  674.  * parseline() -- Split text into a NAGLINE more amenable to processing.
  675.  *
  676.  *    Returns TRUE with the NAGLINE all set up if parsed OK.
  677.  *    Returns FALSE with the line->type set to BAD,
  678.  *              and line->errtype set if undecipherable.
  679.  *
  680.  *
  681.  *    in the code, buf points to the first character not processed,
  682.  *                   cp points to the last character examined.
  683.  *
  684.  *               cp places nulls in likely places.
  685.  *
  686.  *    This is a very ugly function and should be rewritten.
  687.  */
  688. int
  689. parseline( buf, lp )
  690. register char *buf;
  691. register NAGLINE *lp;
  692. {
  693.     register char *cp;
  694.     register int  today;
  695.     register int    i;
  696.     time_t    d;
  697.     time_t    t;
  698.     int        anyday;    
  699.     struct tm ntm;        /* now tm struct */
  700.     struct tm dtm;        /* date tm struct */
  701.     struct tm ttm;        /* time tm struct */
  702.  
  703.     anyday = FALSE;
  704.     lp->line = strcpy( emalloc( strlen( buf ) + 1 ), buf );
  705.  
  706.     /*
  707.      * determine line type, and advance buf to first non-blank after
  708.      * the status field
  709.      */
  710.  
  711.     switch (*buf)
  712.     {
  713.     case COMCHAR:
  714.     lp->type = COMMENT;
  715.     return TRUE;
  716.     /*NOTREACHED*/
  717.  
  718.     case SILCHAR:
  719.     lp->type = SILENT;
  720.     buf++;
  721.     break;
  722.  
  723.     default:
  724.     lp->type = PENDING;
  725.     break;
  726.     }
  727.  
  728.     /* skip to non-whitespace */
  729.  
  730.     while( *buf && isspace(*buf))
  731.     buf++;
  732.  
  733.     /* empty line isn't fatal (it's a comment) */
  734.  
  735.     if (!*buf) {
  736.     lp->type = BAD;
  737.     lp->errtype = EMPTY;
  738.     lp->err = buf;
  739.     return TRUE;
  740.     }
  741.  
  742.     /* bracket the day/date, and null terminate it */
  743.  
  744.     for( cp = buf; *cp && !isspace( *cp ); cp++ )
  745.     continue;
  746.     if( *cp ) *cp++ = '\0';
  747.     else *cp = '\0';
  748.  
  749.     /* cp now positioned at char past null, or on null at the end */
  750.  
  751.     /*
  752.      * buf points at the day field; figure out the
  753.      * absolute time of "Midnight" of the right day for the event.
  754.      */
  755.     lp->datestr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
  756.  
  757.     /* figure when midnight of today was */
  758.  
  759.     ntm = *localtime( &Now );
  760.     ntm.tm_sec = 0;
  761.     ntm.tm_min = 0;
  762.     ntm.tm_hour = 0;
  763.  
  764.     if (*buf == '*')
  765.     {
  766.     anyday = TRUE;
  767.     dtm = ntm;
  768.     }
  769.     else
  770.     {
  771.  
  772.     /* parse date */
  773.  
  774.     if( NULL != gdate( buf, &dtm ) )
  775.     {
  776.         DPRINTF(stderr, "not a date, maybe a day\n");
  777.  
  778.         /* maybe it's a day name... */
  779.  
  780.         lowcase( buf );
  781.         if( (i = find( buf, dow )) >= 0 )
  782.         {
  783.         i--;
  784.         today = ntm.tm_wday;
  785.         DPRINTF(stderr, "today %s, event %s\n",
  786.             dow[ today ],
  787.             dow[ i ] );
  788.         if( i < today )
  789.             i += 7;    /* it's next week */
  790.         d = Now + (( i - today ) * HRSECS * 24 );
  791.         dtm = *localtime( &d );
  792.         dtm.tm_sec = 0;
  793.         dtm.tm_min = 0;
  794.         dtm.tm_hour = 0;
  795.         }
  796.         else
  797.         {
  798.         DPRINTF(stderr, "find of %s in dow returned %d\n", buf, i );
  799.         lp->type = BAD;
  800.         lp->errtype = DATEBAD;
  801.         lp->err = buf;
  802.         return FALSE;
  803.         }
  804.     }
  805.     }
  806.  
  807.     d = tm_to_time( &dtm );
  808.     DPRINTF(stderr, "parseline: date %s\n", nctime(&d) );
  809.  
  810.     /* advance to time */
  811.  
  812.     for( buf = cp ; *buf && isspace(*buf); buf++)    /* skip blanks */
  813.     continue;
  814.  
  815.     if (!*buf) {
  816.     lp->type = BAD;
  817.     lp->errtype = NOTIME;
  818.     lp->err = buf;
  819.     return FALSE;
  820.     }
  821.  
  822.     /* bracket the time */
  823.  
  824.     for( cp = buf; *cp && !isspace( *cp ); cp++ )
  825.     continue;
  826.     if( *cp ) *cp++ = '\0';
  827.     else *cp = '\0';
  828.  
  829.     /*
  830.      * buf now at time field, figure offset until event,
  831.      * then fill in absolute time.
  832.      *
  833.      * gtime can't fail -- it will say it's 00:00 if it
  834.      * doesn't understand.
  835.      */
  836.     DPRINTF(stderr, "parseline: time buf %s\n", buf );
  837.     lp->timestr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
  838.     (void) gtime( buf, &ttm );
  839.     t = (ttm.tm_hour * HRSECS) + (ttm.tm_min * 60);
  840.     lp->atime = d + t;
  841.  
  842.     /*
  843.     ** If past the event, and it's for any day, do it tomorrow. 
  844.     ** BUG:  This breaks if there is an interval after the event
  845.     ** This is a rare case, and I haven't yet thought of a clean fix.
  846.     */
  847.     if( anyday && lp->atime < Now )
  848.         lp->atime += HRSECS * 24;
  849.  
  850.     DPRINTF(stderr, "parseline: time offset %s is %d seconds, %02d:%02d\n",
  851.         buf, t, t / HRSECS, t % HRSECS );
  852.     DPRINTF(stderr, "parseline: etime %s\n", nctime(&lp->atime));
  853.  
  854.     /* advance to intervals */
  855.  
  856.     for( buf = cp; *buf && isspace(*buf); buf++)
  857.     continue;
  858.  
  859.     if (!*buf)
  860.     {
  861.     lp->type = BAD;
  862.     lp->errtype = NOINTERVALS;
  863.     lp->err = buf;
  864.     return FALSE;
  865.     }
  866.  
  867.     /* bracket the intervals */
  868.  
  869.     for( cp = buf; *cp && !isspace( *cp ); cp++ )
  870.     continue;
  871.     if( *cp ) *cp++ = '\0';
  872.     else *cp = '\0';
  873.  
  874.     /* save the interval string. */
  875.  
  876.     lp->intstr = strcpy( emalloc( strlen( buf ) + 1 ), buf );
  877.  
  878.     /* take rest of the line as the command */
  879.  
  880.     if (!*cp)
  881.     {
  882.     lp->type = BAD;
  883.     lp->errtype = NOCMD;
  884.     lp->err = strcpy( emalloc ( strlen( cp ) + 1 ), cp );
  885.     return FALSE;
  886.     }
  887.  
  888.     lp->cmd = strcpy( emalloc ( strlen( cp ) + 1 ), cp );
  889.  
  890.     return TRUE;
  891. }
  892.  
  893.  
  894. /*----------------
  895.  *
  896.  * zaplines() -- delete all NAGLINEs and free their space
  897.  *
  898.  */
  899. void
  900. zaplines()
  901. {
  902.     register NAGLINE *lp;
  903.     register NAGLINE *nlp;
  904.  
  905.     for( lp = Flist; lp ; lp = nlp )
  906.     {
  907.     nlp = lp->next;
  908.  
  909.     if( lp->line )
  910.         free(lp->line);
  911.     if( lp->datestr )
  912.         free(lp->datestr);
  913.     if( lp->timestr )
  914.         free(lp->timestr);
  915.     if( lp->intstr )
  916.         free(lp->intstr);
  917.     if( lp->cmd )
  918.         free(lp->cmd);
  919.  
  920.     free( lp );
  921.     }
  922.     Flast = Flist = NULL;
  923. }
  924.  
  925.  
  926. /*----------------
  927.  *
  928.  * buildq() --  Rebuild the event queue if the .nag file has changed.
  929.  *
  930.  */
  931. void
  932. buildq()
  933. {
  934.     register NAGLINE *lp;
  935.  
  936.     DPRINTF(stderr, "buildq: rebuilding the event queue\n");
  937.  
  938.     zapq();
  939.  
  940.     for( lp = Flist; lp; lp = lp->next )
  941.     {
  942.     /* add events for silenced lines too. */
  943.     if( lp->type != COMMENT )
  944.         addevents( lp );
  945.     }
  946.  
  947.     sortq();
  948.  
  949.     if(Debug)
  950.     showevents( "Event queue after rebuild and sort\n" );
  951. }
  952.  
  953.  
  954. /*----------------
  955.  *
  956.  * zapq() -- Destroy an event queue, setting the head back to NULL.
  957.  *
  958.  * Only the actual element is freed.
  959.  */
  960. void
  961. zapq()
  962. {
  963.     register EVENT *this;
  964.     register EVENT *next;
  965.  
  966.     for ( this = Evq; this ; this = next )
  967.     {
  968.     next = this->next;
  969.     free( this );
  970.     }
  971.     Evq = NULL;
  972. }
  973.  
  974. /*----------------
  975.  *
  976.  * insq() -- Add a new EVENT to the head of a queue.
  977.  *
  978.  */
  979. void
  980. insq( etime, offset, lp )
  981. time_t etime;
  982. register int offset;
  983. NAGLINE *lp;
  984. {
  985.     register EVENT *ep;
  986.  
  987.     etime += (offset * 60);
  988.  
  989.     /* add events after last time we ran, but no more than 24 hours
  990.        in the future */
  991.  
  992.     if( ( etime >= Now || ( Last && etime > Last ) )
  993.        && etime < ( Now + ( HRSECS * 24 ) ) )
  994.     {
  995.     DPRINTF(stderr, "insq: Adding %s at %s\n", lp->cmd, nctime(&etime) );
  996.     }
  997.     else            /* too late */
  998.     {
  999.     DPRINTF(stderr, "insq: Dropping %s at %s\n", lp->cmd, nctime(&etime) );
  1000.     return;
  1001.     }
  1002.  
  1003.     /*ALIGNOK*/
  1004.     ep = (EVENT *) emalloc( sizeof(*ep) );
  1005.     ep->etime = etime;
  1006.     ep->offset = offset;
  1007.     ep->lp = lp;
  1008.  
  1009.     /* splice into the head of the queue */
  1010.     ep->next = Evq;        /* NULL, if last event */
  1011.     Evq = ep;
  1012. }
  1013.  
  1014.  
  1015. /*----------------
  1016.  *
  1017.  * addevents() -- Add pending events for the NAGLINE to the queue.
  1018.  *
  1019.  * Events in the past are not considered.
  1020.  * If the command has been silenced, don't do the command.
  1021.  *
  1022.  */
  1023. void
  1024. addevents( lp )
  1025. register NAGLINE *lp;
  1026. {
  1027.     register char *cp;        /* ptr into the interval string */
  1028.     int offset;            /* offset in minutes */
  1029.  
  1030.     /* for every numeric value in the interval string... */
  1031.  
  1032.     for( cp = lp->intstr; cp && *cp ; cp = index( cp, ':' ) )
  1033.     {
  1034.     if (*cp == ':')        /* skip past optional ':' */
  1035.         cp++;
  1036.     if (!*cp)        /* ignore trailing ':' */
  1037.         return;
  1038.  
  1039.     /* read (possibly) signed interval value */
  1040.  
  1041.     if( 1 != sscanf( cp, "%d", &offset ) )
  1042.     {
  1043.         (void) fprintf(stderr, "%s: bad intervals '%s'\n", Myname,
  1044.                lp->intstr );
  1045.         return;
  1046.     }
  1047.     insq( lp->atime, offset, lp );
  1048.     }
  1049. }
  1050.  
  1051.  
  1052.  
  1053. /*----------------
  1054.  *
  1055.  * timecmp() -- Compare time of two events.
  1056.  *
  1057.  * Made slightly tricky since it must return an int, not a time_t.
  1058.  *
  1059.  */
  1060. int
  1061. timecmp( a, b )
  1062. register EVENT **a;
  1063. register EVENT **b;
  1064. {
  1065.     time_t val = (*a)->etime - (*b)->etime;
  1066.  
  1067.     return( val < 0 ? -1 : val > 0 );
  1068. }
  1069.  
  1070.  
  1071. /*----------------
  1072.  *
  1073.  * sortq() -- Sort the event queue into chronological order.
  1074.  *
  1075.  * 1. Create an array of pointers to the events in the queue.
  1076.  * 2. Sort the array by time of the pointed-to events.
  1077.  * 3. Rebuild the queue in the order of the array.
  1078.  *
  1079.  */
  1080. void
  1081. sortq()
  1082. {
  1083.     register unsigned int n;    /* number of events in the queue */
  1084.     register unsigned int i;    /* handy counter */
  1085.     register EVENT **events;    /* allocated array of EVENT ptrs */
  1086.     register EVENT **ap;    /* ptr into allocated events */
  1087.     register EVENT *ep;        /* pointer in event chain */
  1088.  
  1089.     forward int timecmp();
  1090.  
  1091.     n = 0;
  1092.     for( ep = Evq; ep; ep = ep->next )
  1093.     n++;
  1094.  
  1095.     DPRINTF(stderr, "sortq:  %d events\n", n );
  1096.  
  1097.     if ( n < 2 )
  1098.     return;
  1099.  
  1100.     /* build array of ptrs to events */
  1101.  
  1102.     /*ALIGNOK*/
  1103.     ap = events = (EVENT **) ecalloc( (unsigned)sizeof(**ap), n );
  1104.  
  1105.     /* build array of ptrs to events */
  1106.     for( ep = Evq; ep; ep = ep->next )
  1107.     *ap++ = ep;
  1108.  
  1109.     /* sort by ascending time */
  1110.     (void) qsort( events, (unsigned)n, sizeof(*events), timecmp );
  1111.  
  1112.     /* rechain the event queue from the sorted array */
  1113.     Evq = ep = events[0];
  1114.     for ( i = 0 ; i < n ; )
  1115.     {
  1116.     ep->next = events[i++];
  1117.     ep = ep->next;
  1118.     }
  1119.     ep->next = NULL;
  1120.  
  1121.     free( events );
  1122. }
  1123.  
  1124.  
  1125. /*----------------
  1126.  *
  1127.  * runq() -- Execute all events that are due.
  1128.  *
  1129.  * Sleep until the next scheduled event.  If there are none, or
  1130.  * next is far away, sleep for MINSLEEP and try again.
  1131.  *
  1132.  */
  1133. void
  1134. runq()
  1135. {
  1136.     char cmd[ 5120 ];
  1137.     char now[ CTIMELEN ];
  1138.     register EVENT *evq;    /* standin for global Evq in loop */
  1139.     register EVENT *ep;        /* next event */
  1140.     register NAGLINE *lp;
  1141.     int dsecs;
  1142.  
  1143.     DPRINTF(stderr, "runq start at %s\n", Nowstr );
  1144.  
  1145.     evq = Evq;            /* fast access, be sure to save back */
  1146.  
  1147.     /*
  1148.      * Execute commands that are due.
  1149.      *
  1150.      * Keeps head of the queue current by cutting out events as
  1151.      * they are processed.
  1152.      *
  1153.      * The loop breaks out when the queue is gobbled up,
  1154.      * or we get to an event that is not due now.
  1155.      */
  1156.  
  1157.     while( evq && evq->etime <= Now )
  1158.     {
  1159.     lp = evq->lp;
  1160.  
  1161.     DPRINTF(stderr, "due at %s:\n", nctime( &evq->etime ) );
  1162.  
  1163.     /* Run a PENDING event */
  1164.  
  1165.     if( lp->type == PENDING && lp->cmd )
  1166.     {
  1167.         (void)strcpy( now, &Nowstr[ 11 ] );
  1168.         now[ 5 ] = '\0';
  1169.  
  1170.         (void)sprintf( cmd, "pretime=%d;posttime=%d;now=%s;then=%s;%s\n",
  1171.               -evq->offset,
  1172.               evq->offset,
  1173.               now,
  1174.               nhour( &lp->atime ),
  1175.               lp->cmd );
  1176.  
  1177.         DPRINTF(stderr, "executing:\n%s\n", cmd );
  1178.         if( system( cmd ) )
  1179.         (void) fprintf( stderr, "%s: Trouble running\n'%s'\n",
  1180.                    Myname, cmd );
  1181.     }
  1182.  
  1183.     /* if it's a SILENT event, is it time to make it PENDING? */
  1184.  
  1185.     if( lp->type == SILENT )
  1186.     {
  1187.         /* find the queue end or the next event for the line */
  1188.  
  1189.         for( ep = evq->next ; ep && ep->lp != lp ; ep = ep->next )
  1190.         continue;
  1191.  
  1192.         /* if match, or it was the last in the queue, turn it on */
  1193.  
  1194.         if ( ep )
  1195.         {
  1196.         DPRINTF(stderr, "SILENT event\n");
  1197.         }
  1198.         else
  1199.         {
  1200.         DPRINTF(stderr, "Last SILENT event, making PENDING again.\n");
  1201.         lp->type = PENDING;
  1202.  
  1203.         /*
  1204.          * if the write fails, keep going and hope the user fixes
  1205.          * the nag file.  If we exit, the daemon would need
  1206.          * to be restarted by hand.  Since it won't do anything
  1207.          * but sleep and exit when the user logs off, no harm
  1208.          * is done by sticking around.
  1209.          */
  1210.         (void) writef();
  1211.         }
  1212.     }
  1213.     ep = evq->next;
  1214.     free( evq );
  1215.     evq = ep;
  1216.     }                /* for events on the queue */
  1217.  
  1218.     dsecs = evq ? min( evq->etime - Now, MINSLEEP) : MINSLEEP;
  1219.  
  1220.     DPRINTF(stderr, "sleeping for %d seconds, next %s\n",
  1221.         dsecs,
  1222.         evq ? nctime( &evq->etime ) : "never" );
  1223.  
  1224.     Evq = evq;            /* back to global var */
  1225.  
  1226.     delay( dsecs );
  1227. }
  1228.  
  1229.  
  1230. /*----------------
  1231.  *
  1232.  * emalloc() -- malloc with error msg.
  1233.  *
  1234.  */
  1235. char *
  1236. emalloc( size )
  1237. register int size;
  1238. {
  1239.     register char *ptr;
  1240.     extern char *malloc();
  1241.  
  1242.     if ( ( ptr = malloc( (unsigned) size ) ) == NULL )
  1243.     {
  1244.     (void) fprintf(stderr, "%s: Can't malloc %d bytes\n", Myname, size );
  1245.     exit(1);
  1246.     }
  1247.     return( ptr );
  1248. }
  1249.  
  1250. /*----------------
  1251.  *
  1252.  * ecalloc() -- calloc with error message.
  1253.  *
  1254.  */
  1255. char *
  1256. ecalloc( n, size )
  1257. register unsigned int n;
  1258. register unsigned int size;
  1259. {
  1260.     register char *ptr;
  1261.     extern char *calloc();
  1262.  
  1263.     if ( ( ptr = calloc( (unsigned) size, n ) ) == NULL )
  1264.     {
  1265.     (void) fprintf(stderr, "%s: Can't calloc %d bytes\n", Myname, size * n);
  1266.     exit(1);
  1267.     }
  1268.     return( ptr );
  1269. }
  1270.  
  1271. /*
  1272.  * efopen()  -- fopen with error message on failure (no fatal error)
  1273.  */
  1274. FILE *
  1275. efopen( file, mode )
  1276. char *file;
  1277. char *mode;
  1278. {
  1279.     char buf [ 80 ];
  1280.     register FILE * fp;
  1281.  
  1282.     if( (fp = fopen( file, mode )) == NULL )
  1283.     {
  1284.     (void)sprintf( buf, "%s: can't open file %s with mode \"%s\"",
  1285.               Myname, file, mode );
  1286.     perror( buf );
  1287.     }
  1288.     return( fp );
  1289. }
  1290.  
  1291.  
  1292. /*
  1293.  * showline() -- Dump the line list.
  1294.  */
  1295. void
  1296. showlines( msg )
  1297. char *msg;
  1298. {
  1299.     register NAGLINE *lp;
  1300.  
  1301.     (void) fprintf(stderr, "%s", msg );
  1302.     for( lp = Flist; lp ; lp = lp->next )
  1303.     dumpline( lp );
  1304. }
  1305.  
  1306. /*
  1307.  * dumpline() -- dump a NAGLINE for debugging.
  1308.  */
  1309. void
  1310. dumpline( lp )
  1311. register NAGLINE *lp;
  1312. {
  1313.     if( lp == NULL )
  1314.     {
  1315.     (void) fprintf(stderr, "dumpline: NULL lp\n");
  1316.     return;
  1317.     }
  1318.     (void) fprintf(stderr, "\nline (%s):\n%s\n", linetypes[ lp->type ],
  1319.            lp->line );
  1320.     switch( lp->type )
  1321.     {
  1322.     case BAD:
  1323.     (void) fprintf(stderr, "%s %s\n", parserrs[ lp->errtype ], lp->err );
  1324.     break;
  1325.  
  1326.     case PENDING:
  1327.     case SILENT:
  1328.     (void) fprintf(stderr, "The event is at %s\n", nctime( &lp->atime ));
  1329.     }
  1330. }
  1331.  
  1332. /*
  1333.  * showevents() -- dump the event list, for debugging.
  1334.  */
  1335. void
  1336. showevents( msg )
  1337. char *msg;
  1338. {
  1339.     register EVENT *ep;
  1340.  
  1341.     (void) fprintf(stderr, "%s", msg );
  1342.     for( ep = Evq; ep; ep = ep->next )
  1343.     dumpevent( ep );
  1344. }
  1345.  
  1346. /*
  1347.  * dumpevent() -- print an event, for debugging.
  1348.  */
  1349. void
  1350. dumpevent( ep )
  1351. register EVENT *ep;
  1352. {
  1353.     if( ep == NULL )
  1354.     (void) fprintf(stderr, "dumpevent: NULL ep\n");
  1355.     else
  1356.     (void) fprintf(stderr, "event 0x%x, next 0x%x offset %d time %s\n",
  1357.                ep, ep->next, ep->offset, nctime(&ep->etime) );
  1358. }
  1359.  
  1360. /*
  1361.  * nctime() -- ctime with trailing '\n' whacked off.
  1362.  */
  1363. char *
  1364. nctime( t )
  1365. time_t *t;
  1366. {
  1367.     register char *cp;
  1368.  
  1369.     cp = ctime( t );
  1370.     cp[ strlen( cp ) - 1 ] = '\0';
  1371.     return ( cp );
  1372. }
  1373.  
  1374. /*
  1375.  * nhour() -- return an hh:mm string given a pointer to a time_t.
  1376.  */
  1377. char *
  1378. nhour( t )
  1379. time_t *t;
  1380. {
  1381.     register char *buf = ctime( t );
  1382.  
  1383.     /*
  1384.      * 012345678901234567890123
  1385.      * Wed Dec 31 16:00:00 1969
  1386.      */
  1387.  
  1388.     buf[ 16 ] = '\0';
  1389.     return ( &buf[ 11 ] );
  1390. }
  1391.  
  1392.  
  1393. /*----------------
  1394.  *
  1395.  * delay() -- like sleep but knows what 0 means.
  1396.  *
  1397.  * If user logs out, notices and exit with OK status.
  1398.  *
  1399.  */
  1400. void
  1401. delay( secs )
  1402. int secs;
  1403. {
  1404.     char thislogin[20];
  1405.  
  1406.     if( secs > 0)
  1407.     {
  1408.     (void) sleep( (unsigned) secs );
  1409.     (void) strcpy(thislogin, getlogin());
  1410.     if ( strcmp(Origlogin, thislogin) )
  1411.         exit(0);
  1412.     }
  1413. }
  1414.  
  1415. /*
  1416.  * lowcase() -- make a string all lower case.
  1417.  */
  1418. void
  1419. lowcase( s )
  1420. char *s;
  1421. {
  1422.     while ( *s )
  1423.     {
  1424.     if( isupper( *s ) )
  1425.         *s = tolower( *s );
  1426.     s++;
  1427.     }
  1428. }
  1429.  
  1430. # if 0
  1431.  
  1432. /*
  1433.  * dumptm() -- show contents of a tm structure.
  1434.  */
  1435. dumptm( tm )
  1436. struct tm *tm;
  1437. {
  1438.     (void) fprintf(stderr, "year : %d month: %d day: %d\n",
  1439.            tm->tm_year,tm->tm_mon,tm->tm_mday);
  1440.     (void) fprintf(stderr, "day of month: %d hour: %d minute: %d second: %d\n",
  1441.            tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec) ;
  1442.     (void) fprintf(stderr, "day of year: %d day of week: %d dst: %d\n",
  1443.            tm->tm_yday, tm->tm_wday, tm->tm_isdst) ;
  1444. }
  1445.  
  1446. # endif
  1447.  
  1448. /* end of nag.c */
  1449.  
  1450.  
  1451.