Imported Upstream version 5.1
[debian/gcpegg] / egg.c
1 /* PROGRAM:     eggsh
2  * FILE:        $Header: /home/egg/src/RCS/egg.c,v 1.8 1999/02/28 20:01:43 ghn Exp $
3  * PURPOSE:     EGG site data collection
4  * AUTHOR:      Greg Nelson
5  * DATE:        98-06-20
6  *
7  * REVISED:
8  * $Log: egg.c,v $
9  * Revision 1.8  1999/02/28 20:01:43  ghn
10  * Version 5.1: Added command line parsing for interface/port configuration.
11  * Changed default interface to 0.0.0.0, so that egg will normally bind to
12  * all incoming interfaces.  Reports interface/port it is using, right before
13  * the screen gets cleared and you can't see it.
14  *
15  * Revision 1.7  1999/01/01 23:57:20  ghn
16  * Remove excess code associated with CPU-bound version and "OLDWAY" UI
17  * and egg HW selection. Add back in updated block algorithm description.
18  * Allow non-CPU-bound version to exchange more than 1 packet per second.
19  * Get rid of warnings when network is not reachable, assuming this is a
20  * transient error.
21  *
22  * Revision 1.6  1998/12/31 22:07:56  ghn
23  * Rev 5 code: includes multi-reg support, HTML, etc.
24  *
25  * Revision 1.5  1998/08/03 20:35:55  kelvin
26  * PACKETDUMP support.
27  * 
28  * Revision 1.4  1998/08/01  21:34:26  ghn
29  * Connected user interface and added PSEUDO suppor into main line.
30  *
31  * Revision 1.3  1998/08/01 18:51:25  ghn
32  * Added John's byte-order-independence changes.
33  * 
34  * Revision 1.2  1998/08/01 17:04:26  ghn
35  * Lots of fixes from John, plus DND support.
36  * 
37  * Revision 1.1  1998/07/21 11:41:35  ghn
38  * Initial revision
39  * 
40  * Copyright 1998 - Greg Nelson
41  */
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <signal.h>
47 #include <unistd.h>
48 #include <sys/types.h>
49 #include <sys/socket.h>
50 #include <netdb.h>
51 #include <netinet/in.h>
52
53 #include "global.h"
54 #include "genlib.h"
55 #include "storage.h"
56 #include "network.h"
57 #include "eggui.h"
58 #include "errnos.h"
59 #include "regs.h"
60 #include "regtable.h"
61 #include "version.h"
62
63 /* Good old Linux doesn't define the following functions
64    by default in the #include files the manual page claims
65    it does, and the conditional declarations with __USE_xx
66    doesn't work.  Explicitly declare them, which doesn't
67    seem to do any harm on systems where these functions are
68    properly declared.  If you get errors on the following
69    declarations, you can probably get around them by #ifdef-ing
70    the declarations off for your platform. */
71
72 extern int nice(int inc);
73 extern int strcasecmp(const char *s1, const char *s2);
74
75 /*  Prototypes for forward functions.  */
76
77 static void MakeAwake(AwakePacket *pkt);
78 static void MakeDataPkt(char **pkt, EggCarton *src);
79 static void LoadRCFile(char *filename);
80 static double SetCollOpts(void);
81 static int GreetBasket(void);
82 static void handle_sigkill(int arg), handle_sighup(int arg);
83
84 EggEntry eggtable[MAX_EGGS];
85 BasketEntry baskettable[MAX_BASKETS];
86 EggHeader protocol;
87 REG_driver *configuredREG = NULL;     /* Configured REG driver */
88
89 short numeggs = 0, numbaskets = 0;
90
91 static DevOpts devopts;               /* Device options for REG */
92 static CollectRecord coll;
93 static EggCarton savebuffer;
94 char    *pgmname;                     /* Program name from argv[0] */
95 char    *myaddr;                      /* Interface to bind */
96 int16   myport;                       /* Service port to bind */
97 int32 lastDataSent = 0;               /* Time last packet sent to basket */
98
99 /* If no priority increment has been specified at compile time, set to
100    our default of ±10. [+/- 10, for folks without 8bit editors] */
101
102 #ifndef NICE
103 #define NICE    10
104 #endif
105 static int niceness = NICE;           /* Priority increment/decrement for collection */
106
107 /* Status exported to user interface. */
108
109 int32 time_latency = 0, time_housekeeping = 0;
110
111 #ifdef ALT_UI
112 static double mean_Packet = 0.0, mean_Grand = 0.0;
113 static int32 total_Packets = 0;
114 #endif
115
116 /*  resetCarton  --  Clear existing data from savebuffer and
117                      reinitialise for a sampling interval
118                      beginning at the specified start_time.  */
119
120 static void resetCarton(uint32 start_time)
121 {
122     int i;
123     uint32 sec_pkt;
124
125     /* Save current collection options to packet. */
126
127     memcpy(&(savebuffer.hdr), &(coll.opts), sizeof(EggHeader));
128
129     /* Set number of records in packet and clear record
130        buffers to missing data. */
131
132     savebuffer.hdr.numrec = savebuffer.hdr.rec_pkt; /* Mark all records present */
133
134     /* Align start_time to even multiple of seconds per
135        packet. */
136
137     sec_pkt = savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt;
138     start_time = sec_pkt * (start_time / sec_pkt);
139
140     for (i = 0; i < savebuffer.hdr.rec_pkt; i++) {
141         savebuffer.records[i].timestamp = start_time;
142         start_time += savebuffer.hdr.sec_rec;
143 #if MAXBITS < 256
144         memset(savebuffer.records[i].trials, EGG_MISSING_DATA, MAXSAMP_REC);
145 #else
146 #error "Can't represent MAXBITS in a byte value."
147 #endif
148     }
149 }
150
151 /*  saveProtocol  --  Save current protocol in the .eggprotocolrc file.  */
152
153 static void saveProtocol(void)
154 {
155     FILE *rcfile;
156
157     rcfile = fopen(".eggprotocolrc", "w");
158     if (rcfile == NULL) {
159         fprintf(stderr, "%s: Cannot create .eggprotocolrc file.\n", pgmname);
160         exit(-1);
161     }
162     fprintf(rcfile, "#\n#  Current protocol for egg\n#\n");
163     fprintf(rcfile, "#   PROTOCOL <samp_rec> <sec_rec> <rec_pkt> <trialsz>\n");
164     fprintf(rcfile, "PROTOCOL %d %d %d %d\n", protocol.samp_rec, protocol.sec_rec,
165         protocol.rec_pkt, protocol.trialsz);
166     fclose(rcfile);
167 }
168
169 static void Usage(void) {
170   printf("Usage: %s [-i interface] [-p port]\n", pgmname);
171   exit(-1);
172 }
173
174 /*  Main program.  */
175
176 int main(int argc, char *argv[]) {
177   char                  *pktbuf, *outpktbuf;
178   uint16                pkttype, pktsize;
179   int                   sdlisten, res;
180   struct sockaddr_in    rhost;
181   EggCarton             retrcart;
182   int                   i, isbasket;
183   char                  *remname;
184   int32                 lastconn, lastsend;
185   double                sps;
186 #ifdef SIGACTION_WORKING
187   struct sigaction      sigact;
188 #endif
189   FILE *pidfile, *rcfile;
190
191   pgmname = argv[0];
192   argv++; argc--;
193
194   /* Defaults correspond to original usage */
195   myport = EGGPORT;
196   myaddr = "0.0.0.0";
197
198   while (argc) {
199     if (argv[0][0] == '-') {
200       switch(argv[0][1]) {
201       case 'i': /* Specify interface */
202         if (argc < 2) Usage();
203         myaddr = argv[1];
204         argv++; argc--;
205         break;
206       case 'p': /* Specify port */
207         if (argc < 2) Usage();
208         myport = atoi(argv[1]);
209         if (myport < 0) Usage();
210         argv++; argc--;
211         break;
212       default:  /* Oops. */
213         Usage();
214       }
215     } else {
216       Usage();
217     }
218     argv++; argc--;
219   }
220
221 #ifdef Solaris
222   /* Solaris has an eccentric definition of sigaction() which
223      doesn't seem to even work according to Sun's own
224      documentation.  (sa_flags is a 4 element array of
225      unsigned longs, with no mention of how one stores the
226      flags into it.)  Let's just use plain old signal for
227      the moment. */
228   signal(SIGKILL, handle_sigkill);
229   signal(SIGHUP, handle_sighup);
230 #else
231 #ifdef SIGACTION_WORKING
232   sigact.sa_handler = handle_sigkill;
233   sigact.sa_mask = 0;
234   sigact.sa_flags = 0;
235   sigact.sa_restorer = NULL;
236   sigaction(SIGKILL, &sigact, NULL);
237
238   sigact.sa_handler = handle_sighup;
239   sigaction(SIGHUP, &sigact, NULL);
240 #else
241   signal(SIGKILL, handle_sigkill);
242   signal(SIGHUP, handle_sighup);
243 #endif
244 #endif
245
246   fprintf(stderr, "Starting egg %s.\n", Version);
247
248   /* Save our process ID in a file for folks who might wish
249      to send us a signal. */
250
251   pidfile = fopen("eggsample.pid", "w");
252   if (pidfile == NULL) {
253     fprintf(stderr, "Unable to create eggsample.pid file.\n");
254     exit(-1);
255   }
256   fprintf(pidfile, "%d\n", getpid());
257   fclose(pidfile);
258
259   /* If we aren't being run by the super-user, disable the
260      priority raising and lowering mechanism. */
261
262   if (getuid() != 0) {
263     niceness = 0;
264   }
265
266 #ifdef DEBUG
267   printf("User ID = %d.  Niceness = %d.\n", getuid(), niceness);
268   printf("REG drivers installed: ");
269   for (i = 0; reg_table[i] != NULL; i++) {
270     printf(" %s", reg_table[i]->reg_name);
271   }
272   printf("\n");
273 #endif
274
275   LoadRCFile(NULL);
276
277   /* If an auxiliary configuration file exists, load it on
278      top of the arguments loaded from the main RC file.  If
279      it doesn't exist, create it. */
280
281   if ((rcfile = fopen(".eggprotocolrc", "r")) != NULL) {
282     fclose(rcfile);
283     LoadRCFile(".eggprotocolrc");
284   } else {
285     saveProtocol();
286   }
287
288   if (configuredREG == NULL) {
289     fprintf(stderr, "%s: RC error, no REG specified for egg.\n",
290         pgmname);
291     exit(-1);
292   }
293 #ifdef DEBUG
294     else {
295     printf("REG configured: %s = %d, %d, %ld\n", configuredREG ->reg_name,
296         devopts.type, devopts.port, devopts.baud);
297   }
298 #endif
299
300
301   sps = SetCollOpts();
302
303   if ((coll.dd = OpenDev(&devopts)) < 0) {
304     fprintf(stderr, "Couldn't talk to hardware device.\n");
305     exit(1);
306   }
307
308   if (EvalSpeed(coll.dd) < coll.opts.trialsz * sps) {
309     fprintf(stderr, "Requested speed exceeds device capabilities.\n");
310     exit(-1);
311   }
312
313 #if REPORT > 0
314   if (myaddr) {
315     fprintf(stderr, "TCP configured: %s.%d\n", myaddr, myport);
316   } else {
317     fprintf(stderr, "TCP configured: hostname.%d\n", myport);
318   }
319 #endif
320
321   UIInit();
322
323   sdlisten = -1;        /* No conn yet. */
324   lastconn = lastsend = 0;
325
326   /* Initialise carton to zero time, indicating no data in
327      initial carton. */
328   resetCarton(0);
329
330   /* Initialize storage system */
331   InitStorage("%Y-%m/%Y-%m-%d-$06E");
332
333   /* Inner loop is currently two threads which are both processed
334      together through NBIO.
335
336      2A. Adjust priority and sleep until time computed below (2H).
337      2B. Busy wait until second ticks over.
338      2C. Empty REG input buffer (if needed), then collect sample.
339      2D. Lower priority to handle bookkeeping; save packet.
340      2E. Update user interface.
341
342      2F. Send awake packet if connival is passed.
343      2G. [DND only] Put net to sleep if we've been waiting more than a minute
344
345      1A. Wait for connection.  When a connection comes in,
346          packet has been decrypted and verified.
347      1B. Validate connection.
348      1C. Reply to connection.
349      1D. If we have more than MINSLEEP time left in second,
350          go back to 1A.
351
352      2H. Compute new sleep time.
353   */
354
355   while (1) {
356     static int sleeptime = 0;
357     static struct timeval t, lt = {0, 0};
358     struct timeval ct;
359     trial sample;
360     int havedata;
361     uint32 rindex;
362 #ifdef DEBUG
363     uint32 msec, usec;
364 #endif
365
366 #ifndef SLACK
367 #define SLACK   50000                 /* CPU loop resynchronisation time in microseconds */
368 #endif
369
370 #ifndef MINSLEEP
371 #define MINSLEEP        100000          /* Minimum sleep time, usec */
372 #endif
373
374     /* 2A. If we have successfully computed a sleep time until
375        the window opens to collect the next sample, give up
376        the CPU until that time arrives. */
377
378     if (niceness != 0) {
379         nice(-niceness);              /* Raise priority for re-dispatch */
380     }
381     if (sleeptime > 0) {
382         Usleep((unsigned) sleeptime);
383     }
384
385     /* 2B. We're now close to the start of the next second.  Watch the
386        time until the second changes, then collect the next sample.
387        Note that this method of doing things enforces the one
388        sample/second that has become standard practise.  The code at
389        the bottom of the loop could be changed to allow for a longer
390        interval, but this code enforces a minimum of a 1-second change
391        for each sample. */
392
393     while (1) {
394       gettimeofday(&t, NULL);
395       if (lt.tv_sec == 0) lt.tv_sec = t.tv_sec;
396       if (t.tv_sec != lt.tv_sec) break;
397     }
398
399     /* 2C. It is now time to collect the next sample.  Discard any
400        any data in the input queue and get the sample.  */
401
402     Discard(coll.dd);
403     sample = Sample(coll.dd, coll.opts.trialsz);
404     if (niceness != 0) {
405       nice(niceness);         /* Collection done.  Lower priority to normal. */
406     }
407
408     /* 2D. Okay, we're now in "quality time"--the slack after collection
409        of a sample until the time for the next sample arrives.
410        Now is an excellent time to do all kinds of housekeeping.
411        First of all, see if the sample just collected fits into
412        the packet currently being assembled.  If not, we need to
413        save the last packet in the egg data file and initialise
414        a new packet for the time period in which this sample was
415        collected. */
416
417     if ((savebuffer.records[0].timestamp == 0) ||
418         ((savebuffer.records[0].timestamp +
419           (savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt)) <= t.tv_sec)) {
420
421       if (savebuffer.records[0].timestamp != 0) {
422         static int firstpacket = 1;
423 #ifdef ALT_UI
424         int r, t;
425         uint32 ptrials = 0, psum = 0;
426         static uint32 totalTrials = 0;
427         static double totalGrand = 0.0;
428 #endif
429
430         /* Sample isn't within current packet.  Dump packet to the egg
431            data file and reinitialise to the window containing the
432            sample we just collected.  But first, a little gimmick.  If
433            this is the first packet we've collected and it contains
434            one or more missing samples at the beginning, discard it.
435            This keeps the routine missing samples in the first packet
436            after the egg starts up from being reported as missing
437            samples due to genuine synchronisation errors. */
438
439         if (!(firstpacket && (savebuffer.records[0].trials[0] == EGG_MISSING_DATA))) {
440           SavePacket(&savebuffer);
441         }
442 #ifdef DEBUG
443         else {
444           fprintf(stderr, "Dropping first packet to discard start-up missing samples.\n");
445         }
446 #endif
447         firstpacket = 0;
448
449 #ifdef ALT_UI
450         /* Update the last packet and grand mean data for the debug
451            interface status display. */
452
453         for (r = 0; r < savebuffer.hdr.rec_pkt; r++) {
454           for (t = 0; t < savebuffer.hdr.samp_rec; t++) {
455             if (savebuffer.records[r].trials[t] != EGG_MISSING_DATA) {
456               ptrials++;
457               psum += savebuffer.records[r].trials[t];
458             }
459           }
460         }
461         mean_Packet = ((double) psum) / ptrials;
462         totalGrand += psum;
463         totalTrials += ptrials;
464         mean_Grand = totalGrand / totalTrials;
465         total_Packets++;
466         printf("Packets sent: %ld  Packet mean: %6.2f  Grand mean: %6.2f\n",
467                 total_Packets, mean_Packet, mean_Grand);
468 #endif
469       }
470
471       /* Check for change to the collection parameters...
472          now is the time to implement it. */
473
474       if (coll.opts.rec_pkt != protocol.rec_pkt ||
475           coll.opts.trialsz != protocol.trialsz ||
476           coll.opts.sec_rec != protocol.sec_rec ||
477           coll.opts.samp_rec != protocol.samp_rec) {
478         sps = SetCollOpts();
479         saveProtocol();
480       }
481       /* Reinitialise carton to interval containing sample */
482       resetCarton(t.tv_sec);
483     }
484
485     /* Store the sample into the carton, which is now guaranteed to
486        bracket the interval in which it was collected. */
487
488 #ifdef DEBUG
489     /* But let's be sure it really *is* in the interval. */
490     if (t.tv_sec < savebuffer.records[0].timestamp ||
491         t.tv_sec >= (savebuffer.records[0].timestamp + savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt)) {
492       fprintf(stderr, "***Sample, collected at %ld, is not within packet starting at %ld.\n",
493               t.tv_sec, savebuffer.records[0].timestamp);
494       exit(-1);
495     }
496 #endif
497
498     rindex = t.tv_sec - savebuffer.records[0].timestamp;
499     savebuffer.records[rindex / savebuffer.hdr.sec_rec].trials[rindex % savebuffer.hdr.sec_rec] = sample;
500
501     /* 2E. Update user interface. */
502
503 #ifndef NO_UI
504     coll.data.trials[coll.sampct] = sample;
505     coll.sampct++;
506     UIUpdate(coll.sampct >= coll.opts.samp_rec, &coll);
507
508     if (coll.sampct >= coll.opts.samp_rec) {
509         coll.sampct = 0;
510     }
511 #endif
512
513     /* Now that the current sample has been dealt which, check
514        for egg-initiated actions whose time has come.  */
515
516     /* 2F. First of all, it it's time to prod the basket with an
517        awake packet, lob one over the pole. */
518
519     if (getzulutime(NULL) - lastconn > (eggtable[0].connival * 60L)) {
520       lastsend = lastconn = getzulutime(NULL);
521       
522       /* If this is a dial-and-drop egg, connect to the network before
523          sending the packet. */
524
525       if (sdlisten < 0 && eggtable[0].conntype == CONN_DND) {
526         sdlisten = NetUp(eggtable[0].upcmd, myaddr, myport);
527       }
528       
529       GreetBasket();
530     }
531     
532     /* 2G. If this is a dial-and-drop egg and it's been more than a
533        minute since we sent anything, tear down the network connection. */
534     
535     if (eggtable[0].conntype == CONN_DND && sdlisten >= 0) {
536       if ((getzulutime(NULL) - lastsend) > 60L) {
537         NetDown(eggtable[0].dncmd, sdlisten);
538         sdlisten = -1;
539       }
540     }
541
542     /* Now deal with basket-initiated requests received on the socket
543        since the last time around the loop.  To improve throughput for
544        dialup connections, we may come back here several times if we
545        have more data and if time allows.  */
546
547     do {
548       havedata = 0;     /* Don't loop unless net activity happens. */
549
550       /* 1A. Wait for connection. */
551       if (eggtable[0].conntype == CONN_PERM) {
552         if (sdlisten < 0) {
553           /* Initialize networking, get a listening socket at EGGPORT */
554           sdlisten = InitNetwork(myaddr, myport);
555         }
556       }
557       if (sdlisten >= 0) {
558         res = NetListen(sdlisten, &pktbuf, &rhost, FALSE);
559         if (res < 0 && res != ERR_COMM_TMOUT) {
560           fprintf(stderr, "NetListen error: %d\n", res);
561           break;        /* Out of do loop */
562         }
563         /* Data present, process networking thread */
564         if (res == ERR_NONE) {
565           /* 1B. Validate connection. 
566              Remote host address is in rhost.  Make sure this is either an
567              egg or a basket, and set the flag appropriately. */
568           isbasket = 0;
569           for (i = 0; i < numbaskets; i++) {
570             if (!memcmp(&(rhost.sin_addr),
571                         &(baskettable[i].ipaddr.sin_addr),
572                         sizeof(rhost.sin_addr))) {
573               isbasket = 1;
574               remname = baskettable[i].name;
575             }
576           }
577           
578           if (!isbasket) {
579             fprintf(stderr, "Attempt to connect from unknown source: %s",
580                     sockaddr2dquad(&rhost));
581             break;      /* Out of do loop */
582           }
583
584           havedata = 1; /* Initial assumption, until disproven. */
585           
586           memcpy(&pkttype, pktbuf, sizeof(uint16));
587           memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
588           pkttype = ntohs(pkttype);
589           pktsize = ntohs(pktsize);
590           
591           /* 1C. Reply to connection. */
592           switch(pkttype) {
593           case DATA_PACKET:
594           case AWAKE_PACKET:
595             /* Not acceptable at an eggsite. */
596             fprintf(stderr, "%s: EGG could not accept %d packet\n", 
597                     pgmname, pkttype);
598             break;
599             
600             /* Request for data from a basket. */
601             
602           case REQ_PACKET:
603             {
604               uint16 reggid;
605               uint32 stime;
606               char *pktP = pktbuf + (2 * sizeof(uint16));
607               
608               unpackShort(reggid);
609               unpackLong(stime);
610 #ifdef PACKETDUMP
611               fprintf(stderr, "Request: eggid = %d, starttm = %ld: %s",
612                       reggid, stime, asctime(gmtime((time_t *) &stime)));
613 #endif
614               res = LoadPacket(stime, reggid, &retrcart);
615             }
616             if (res == ERR_NONE) {
617               /* NetPacketize it */
618               MakeDataPkt(&outpktbuf, &retrcart);
619               rhost.sin_port = htons(BASKETPORT);
620               res = NetTalk(&rhost, outpktbuf, TRUE);
621               if (res < 0) {
622                 fprintf(stderr, "NetTalk failed (%d)\n", res);
623               }
624               free(outpktbuf);
625               lastDataSent = lastsend = getzulutime(NULL);
626             } else if (res == ERR_EOF) {
627               /* End of data.  If DND, bring down connection */
628               if (eggtable[0].conntype == CONN_DND) {
629                 NetDown(eggtable[0].dncmd, sdlisten);
630                 sdlisten = -1;
631               }
632               havedata = 0;
633             }
634             break;
635             
636           case SETTINGS_PACKET:
637             /* Update settings in buffer, will change at next packet
638                boundary. */
639             
640             { char *pktP = pktbuf + (3 * sizeof(uint16)) + sizeof(uint32);
641             
642             unpackShort(protocol.samp_rec);
643             unpackShort(protocol.sec_rec);
644             unpackShort(protocol.rec_pkt);
645             unpackByte(protocol.trialsz);
646             }
647 #ifdef PACKETDUMP
648             fprintf(stderr, "Settings: samp_rec = %d, sec_rec = %d, rec_pkt = %d, trialsz = %d\n",
649                     protocol.samp_rec, protocol.sec_rec, protocol.rec_pkt, protocol.trialsz);
650 #endif
651             break;
652           }
653           free(pktbuf);
654         }
655       }
656       gettimeofday(&ct, NULL);
657     } while (havedata && ct.tv_usec < 1000000L - MINSLEEP);
658
659     /* 2H. Compute the length in microseconds we should sleep before
660        the top of the next second.  Note that we compute the interval
661        based on the time at the bottom of the loop, after dealing with
662        whatever housekeeping requests may have been performed since
663        collecting the last sample. */
664
665     gettimeofday(&ct, NULL);
666     if (lt.tv_sec == 0) {
667         lt.tv_sec = t.tv_sec - 1;
668     }
669     time_latency = (((t.tv_sec - lt.tv_sec) - 1) * 1000000L) + t.tv_usec;
670     time_housekeeping = ((ct.tv_sec - t.tv_sec) * 1000000L) + (ct.tv_usec - t.tv_usec);
671 #ifdef DEBUG
672     usec = time_latency;
673     msec = usec / 1000;
674     usec %= 1000;
675     printf("Sampling latency: %ld.%03ld msec", msec, usec);
676
677     usec = time_housekeeping;
678     msec = usec / 1000;
679     usec %= 1000;
680     printf("  Housekeeping time: %ld.%03ld msec\n", msec, usec);
681 #endif
682     lt = ct;
683     sleeptime = ((1000000 - SLACK) - ct.tv_usec);
684     /* printf("Sleep time = %d usec\n", sleeptime); */
685   }
686 }
687
688 /* EGG specific */
689
690 /*  LoadRCFile  --  Read in a configuration file.  If the filename
691                     argument is NULL, the default file is read.
692                     Otherwise, configuration statements are read
693                     from the file given by the argument.  */
694
695 static void LoadRCFile(char *filename) {
696   FILE *fp;
697   char linebuf[200];
698   char *myargv[MAX_PARSE], *tp;
699   int myargc, lcount, p, b, i;
700
701   if (filename != NULL) {
702     if ((fp = fopen(filename, "r")) == NULL) {
703         fprintf(stderr, "%s: Cannot open auxiliary configuration file %s.\n", pgmname, filename);
704         exit(-1);
705     }
706   } else {
707     if ((fp = fopen(".eggrc", "r")) == NULL) {
708       if ((fp = fopen("~/.eggrc", "r")) == NULL) {
709         if ((fp = fopen("/etc/eggrc", "r")) == NULL) {
710           fprintf(stderr, "%s: Couldn't find a egg RC file.\n", pgmname);
711           exit(-1);
712         }
713       }
714     }
715   }
716
717   lcount = 0;
718
719   while(fgets(linebuf, 200, fp) != NULL) {
720     /* Comments and blank lines ignored. */
721     lcount++;
722     if (*linebuf == '#' || *linebuf == '\n') continue;
723     Parse(linebuf, &myargc, myargv);
724     if (myargc == 0) continue;
725     if (!strcmp(myargv[0], "EGG")) {
726       if (myargc < 7 || myargc > 8) {
727         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
728                 pgmname, lcount, myargv[0]);
729         exit(-1);
730       }
731       if (numeggs > 0) {
732         fprintf(stderr, "%s: RC error, line %d, only one EGG allowed\n", 
733                 pgmname, lcount);
734         exit(-1);
735       }
736
737       eggtable[0].name = mallocpy(myargv[1]);
738       eggtable[0].id = atoi(myargv[2]);
739       dquad2sockaddr(&(eggtable[0].ipaddr), NULL, myargv[3]);
740       eggtable[0].primbasket = mallocpy(myargv[4]);
741       eggtable[0].conntype = (!strcmp(myargv[5], "PERM"))?CONN_PERM:CONN_DND;
742       eggtable[0].connival = atoi(myargv[6]);
743       eggtable[0].url = NULL;   /* URL specification is permitted, but ignored */
744       numeggs = 1;
745     } else if (!strcmp(myargv[0], "BASKET")) {
746       if (myargc != 3) {
747         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
748                 pgmname, lcount, myargv[0]);
749         exit(-1);
750       }
751       baskettable[numbaskets].name = mallocpy(myargv[1]);
752       dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
753       numbaskets++;
754     } else if (!strcmp(myargv[0], "PROTOCOL")) {
755       if (myargc != 5) {
756         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
757                 pgmname, lcount, myargv[0]);
758         exit(-1);
759       }
760       protocol.samp_rec = atoi(myargv[1]);
761       protocol.sec_rec = atoi(myargv[2]);
762       protocol.rec_pkt = atoi(myargv[3]);
763       protocol.trialsz = atoi(myargv[4]);
764
765     /*  REG:  Configure Random Event Generator for this egg.  */
766
767     } else if (!strcmp(myargv[0], "REG")) {
768       if (myargc != 4) {
769         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
770                 pgmname, lcount, myargv[0]);
771         exit(-1);
772       }
773       if (configuredREG != NULL) {
774         fprintf(stderr, "%s: RC error, line %d, multiple REGs specified.\n",
775                 pgmname, lcount);
776         exit(-1);
777       }
778       for (i = 0; reg_table[i] != NULL; i++) {
779         if (strcasecmp(myargv[1], reg_table[i]->reg_name) == 0) {
780           configuredREG = reg_table[i];
781           devopts.type = reg_table[i]->reg_type;
782         }
783       }
784       if (configuredREG == NULL) {
785         fprintf(stderr, "%s: RC error, line %d, unknown REG type %s specified.\n",
786                 pgmname, lcount, myargv[1]);
787         exit(-1);
788       }
789         
790       devopts.port = atoi(myargv[2]);
791       devopts.baud = atoi(myargv[3]);
792     } else if (!strcmp(myargv[0], "NETUP") ||
793                !strcmp(myargv[0], "NETDOWN") ||
794                !strcmp(myargv[0], "PORT") ||
795                !strcmp(myargv[0], "INTERFACE")) {
796       if (myargc < 2) {
797         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
798                 pgmname, lcount, myargv[0]);
799         exit(-1);
800       }
801       tp = (char *) malloc(200);
802       for (*tp = 0, p = 1; p < myargc; p++) {
803         strcat(tp, myargv[p]);
804         if (p < myargc-1) strcat(tp, " ");
805       }
806
807       if (!strcmp(myargv[0], "NETUP")) eggtable[0].upcmd = tp;
808       else if (!strcmp(myargv[0], "NETDOWN")) eggtable[0].dncmd = tp;
809       else if (!strcmp(myargv[0], "PORT")) {
810         if (atoi(tp) <= 0) {
811           fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
812                   pgmname, atoi(tp), myport);
813         } else {
814           myport = atoi(tp);
815         }
816         free(tp);
817       } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
818     } else {
819       fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[0]);
820       exit(-1);
821     }
822   }
823
824   /* Consistency checks done only after loading the main
825      configuration file. */
826
827   if (filename == NULL) {
828     for (p = -1, b = 0; b < numbaskets; b++) {
829       if (!strcmp(baskettable[b].name, eggtable[0].primbasket)) p = b;
830     }
831
832     if (p != 0) {
833       fprintf(stderr, "%s: RC error, primary basket should be listed first.\n", pgmname);
834       exit(-1);
835     }
836   } else {
837       printf("Reloaded auxiliary configuration file %s.\n", filename);
838   }
839   fclose(fp);
840 }
841
842 /*  MakeAwake  --  Make a egg awake packet to let the basket
843                    know we're on line.  */
844
845 static void MakeAwake(AwakePacket *pkt) {
846   char *pktP = (char *) pkt;
847
848   packShort(AWAKE_PACKET);
849   packShort((4 * sizeof(uint16)) + sizeof(uint32));
850   packShort(eggtable[0].id);
851   packLong(getzulutime(NULL));
852 }
853
854 /*  MakeDataPkt  --  Build a canonical network byte order data packet
855                      from the data collected in an EggCarton.  */
856
857 static void MakeDataPkt(char **pkt, EggCarton *src) {
858   uint16 pktsize, rec;
859   char *pktP;
860
861   pktsize = (7 * sizeof(uint16)) + sizeof(trial) +          /* Header */
862             (src->hdr.numrec * (sizeof(uint32) +            /* Trial data */
863                 src->hdr.samp_rec * sizeof(trial))) +
864             sizeof(uint16);                                 /* CRC-16 */
865   *pkt = pktP = (char *) malloc(pktsize);
866
867   /* Assemble header fields into data packet. */
868
869   packShort(src->hdr.type = DATA_PACKET);
870   packShort(src->hdr.pktsize = pktsize);
871   packShort(src->hdr.eggid);
872   packShort(src->hdr.samp_rec);
873   packShort(src->hdr.sec_rec);
874   packShort(src->hdr.rec_pkt);
875   packByte(src->hdr.trialsz);
876   packShort(src->hdr.numrec);
877
878   /* Append data records to packet. */
879
880   for (rec = 0; rec < src->hdr.numrec; rec++) {
881     packLong(src->records[rec].timestamp);
882     packBytes(&(src->records[rec].trials), src->hdr.samp_rec);
883   }
884
885   /* No need to calculate CRC -- NetTalk does that for us.
886      Note that we did reserve space for the CRC when
887      allocating the packet buffer. */
888 }
889
890 /*  SetCollOpts  --  Set collection options from the protocol
891                      specified in the .rc file or by a basket.  */
892
893 static double SetCollOpts(void) {
894   double sps;
895
896   coll.opts.eggid = eggtable[0].id;
897   
898   coll.opts.rec_pkt = protocol.rec_pkt;
899   if (coll.opts.rec_pkt < 1) coll.opts.rec_pkt = 1;
900   if (coll.opts.rec_pkt > MAXREC_PKT) coll.opts.rec_pkt = MAXREC_PKT;
901
902   coll.opts.trialsz = protocol.trialsz;
903   if (coll.opts.trialsz < MINBITS) {
904     fprintf(stderr, "Attempt to set trial size below %d, to %d.\n",
905             MINBITS, coll.opts.trialsz);
906     coll.opts.trialsz = MINBITS;
907   }
908   if (coll.opts.trialsz > MAXBITS) {
909     fprintf(stderr, "Attempt to set trial size above %d, to %d.\n",
910             MAXBITS, coll.opts.trialsz);
911     coll.opts.trialsz = MAXBITS;
912   }
913
914   coll.opts.sec_rec = protocol.sec_rec;
915   if (coll.opts.sec_rec < 1) {
916     fprintf(stderr, "Attempt to set seconds/record below 1, to %d.\n",
917             coll.opts.sec_rec);
918     coll.opts.sec_rec = 1;
919   }
920   if (coll.opts.sec_rec > MAXSEC_REC) {
921     fprintf(stderr, "Attempt to set seconds/record above %d, to %d.\n",
922             MAXSEC_REC, coll.opts.sec_rec);
923     coll.opts.sec_rec = MAXSEC_REC;
924   }
925
926   coll.opts.samp_rec = protocol.samp_rec;
927   if (coll.opts.samp_rec < 1) {
928     fprintf(stderr, "Attempt to set samples/record below 1, to %d.\n",
929             coll.opts.samp_rec);
930     coll.opts.samp_rec = 1;
931   }
932   if (coll.opts.samp_rec > MAXSAMP_REC) {
933     fprintf(stderr, "Attempt to set samples/record above %d, to %d.\n",
934             MAXSAMP_REC, coll.opts.samp_rec);
935     coll.opts.samp_rec = MAXSAMP_REC;
936   }
937
938   sps = (double)coll.opts.samp_rec / (double)coll.opts.sec_rec;
939
940   fprintf(stderr, "Effective sample rate is about %f samp/sec or %f bits/sec\n",
941           sps, coll.opts.trialsz * sps);
942   fprintf(stderr, "Packets contain %d records\n", coll.opts.rec_pkt);
943
944   return sps;
945 }
946
947 /*  GreetBasket  --  Send an awake packet to each configured
948                      basket.  */
949
950 static int GreetBasket(void) {
951   int                   i, b;
952   struct sockaddr_in    bhost;
953   AwakePacket           awake;
954
955   MakeAwake(&awake);
956
957   for (b = 0; b < numbaskets; b++) {
958     memset(&bhost, 0, sizeof(struct sockaddr_in));
959     memcpy(&bhost, &(baskettable[b].ipaddr), sizeof(bhost));
960     bhost.sin_port = htons(BASKETPORT);
961     bhost.sin_family = AF_INET;
962
963     /* Don't gripe about network unreachable.  Just means 
964        network is down right now, try later. */
965     i = NetTalk(&bhost, (char *)&awake, FALSE);
966     if (i < 0) {
967       /* Couldn't get to this one, try others. */
968 #ifdef DEBUG
969       fprintf(stderr, "%s: Failure to reach basket '%s'\n", 
970               pgmname, baskettable[b].name);
971 #endif
972     } else {
973       return ERR_NONE;
974     }
975   }
976
977   return ERR_COMM_TMOUT;
978 }
979
980 /*  handle_sigkill  --  KILL signal handler.  Terminate user interface
981                         and exit.  */
982
983 static void handle_sigkill(int arg) {
984   UIClose();
985   exit(-1);
986 }
987
988 /*  handle_sighup  --  HUP signal handler.  Reload protocol from the
989                        .eggprotocolrc file.  */
990
991 static void handle_sighup(int arg) {
992   LoadRCFile(".eggprotocolrc");
993 #ifndef SIGACTION_WORKING
994   signal(SIGHUP, handle_sighup);
995 #endif
996 }