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