Imported Upstream version 5.1
[debian/gcpegg] / basket.c
1 /* PROGRAM:     basket
2  * FILE:        $Header: /home/egg/src/RCS/basket.c,v 1.8 1999/02/28 19:58:49 ghn Exp $
3  * PURPOSE:     Main program for basket software
4  * AUTHOR:      Greg Nelson
5  * DATE:        98-06-28
6  *
7  * REVISED:
8  * $Log: basket.c,v $
9  * Revision 1.8  1999/02/28 19:58:49  ghn
10  * Version 5.1: Support for ip address masks to allow us to limit egg
11  * spoofing to users on a limited subnet (cf. EGG_DYNAMIC).  Added
12  * flexible interface/port cmdline arguments and config file arguments.
13  * Report the eggs we are accepting.
14  *
15  * Revision 1.7  1999/01/01 23:52:31  ghn
16  * Allow basket to start with no net connection and handle connection
17  * going up and down.
18  *
19  * Revision 1.6  1998/12/31 22:07:56  ghn
20  * Rev 5 code: includes multi-reg support, HTML, etc.
21  *
22  * Revision 1.5  1998/08/03 20:40:13  kelvin
23  * PACKETDUMP, SIGHUP reset when signal caught.
24  * 
25  * Revision 1.4  1998/08/01  18:52:49  ghn
26  * Added John's byte-order-independence changes.
27  *
28  * Revision 1.3  1998/08/01 17:02:05  ghn
29  * Incorporate John's Solaris fixes and terminate STAT lines properly.
30  *
31  * Revision 1.2  1998/07/21 11:44:04  ghn
32  * Added headers.
33  *
34  * Copyright 1998 - Greg Nelson
35  */
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <signal.h>
40 #include <string.h>
41 #include <sys/types.h>
42 #include <sys/socket.h>
43 #include <netdb.h>
44 #include <netinet/in.h>
45 #include "global.h"
46 #include "genlib.h"
47 #include "storage.h"
48 #include "network.h"
49 #include "errnos.h"
50 #include "version.h"
51
52 #define EGGSTATS        "egg.status"
53
54 static int32 BasketReceiveDataPacket(char *pktbuf, int16 isegg, int16 thisegg,
55                               struct sockaddr_in *rhost);
56 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence);
57 static void MakeSettings(SettingsPacket *pkt, uint16 eggid);
58 static void LoadRCFile(void);
59 static void LoadEggStats(void);
60 static void SaveEggStats(void);
61 static void handle_sighup(int arg);
62
63 EggEntry eggtable[MAX_EGGS];
64 BasketEntry baskettable[MAX_BASKETS];
65 EggHeader protocol;
66
67 short numeggs, numbaskets;
68 char    *pgmname;                     /* Program name from argv[0] */
69 char    *myaddr;                      /* Interface to bind */
70 int16   myport;                       /* Service port to bind */
71
72 static int htmlInterval = 0;          /* HTML status file update interval in seconds */
73 static char htmlFile[256] = "";       /* HTML status file name */
74 static uint32 upsince = 0;            /* Basket start time */
75 #define htmlReport (htmlFile[0] != 0) /* Make HTML report ? */
76
77 struct {
78     uint32 firstPacket;               /* Time of "first contact" with egg */
79     uint32 trials;                    /* Number of trials received */
80     uint32 missing;                   /* Number of missing samples */
81     double sum;                       /* Sum of trials to date */
82 } eggStatistics[MAX_EGGS];
83
84 /*  updateHTML  --  Update HTML status file if a decent interval
85                     has passed since the last update.  */
86
87 static void updateHTML(void)
88 {
89     if (htmlReport) {
90         int i;
91         static uint32 lastHtml = 0;
92         uint32 now = getzulutime(NULL);
93         char udate[256], ustime[256];
94         static char timeFormat[] = "%Y-%m-%d %T";
95
96         /* When first called, set the last HTML update interval to
97            the present moment.  This defers generation of the first
98            HTML report until htmlInterval elapses.  This prevents
99            issuing a potentially-misleading HTML report based on
100            incomplete information. */
101
102         if (lastHtml == 0) {
103             lastHtml = now;
104         }
105
106         if ((now - lastHtml) >= htmlInterval) {
107             FILE *hf;
108
109             lastHtml = now;
110             hf = fopen(htmlFile, "w");
111             if (hf == NULL) {
112                 fprintf(stderr, "Cannot open HTML status file %s.  Updates discarded.\n", htmlFile);
113                 htmlFile[0] = 0;      /* Suppress further updates */
114                 return;
115             }
116
117             strftime(udate, sizeof udate, timeFormat, gmtime((time_t *) &now));
118             strftime(ustime, sizeof ustime, timeFormat, gmtime((time_t *) &upsince));
119
120             fprintf(hf, "\
121 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n\
122 <html version=\"-//W3C//DTD HTML 3.2 Final//EN\">\n\
123 <head>\n\
124 <title>Basket %s Status Report</title>\n\
125 <meta http-equiv=\"Refresh\" content=\"%d\">\n\
126 </head>\n\
127 <body>\n\
128 <center>\n\
129 <h1>Basket %s Status Report</h1>\n\
130 <h3>Last update: %s UTC</h3>\n\
131 <h4>Basket started at %s UTC<br>\n\
132 %s</h4>\n\
133 </center>\n\
134 <center>\n\
135 <h2>Egg Status</h2>\n\
136 <table border cellpadding=4>\n\
137 <tr><th>Name<th>Number<th>Up Since<th>Last Packet<th>Active\n\
138 <th>Trials<th>Missed<th>Coverage<th>Mean\n\
139 ",
140             baskettable[0].name, htmlInterval, baskettable[0].name, udate, ustime, Version);
141
142             for (i = 0; i < numeggs; i++) {
143               char url1[256], url2[256];
144
145               if ((eggtable[i].lastupd == 0) || (eggStatistics[i].firstPacket == 0)) {
146                   strcpy(udate, "<td colspan=2 align=center><em>Never contacted</em>");
147                   strcpy(ustime, "");
148               } else {
149                   strftime(udate, sizeof udate, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggtable[i].lastupd));
150                   strftime(ustime, sizeof ustime, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggStatistics[i].firstPacket));
151               }
152               if (eggtable[i].url == NULL) {
153                   url1[0] = url2[0] = 0;
154               } else {
155                   sprintf(url1, "<a href=\"%s\">", eggtable[i].url);
156                   strcpy(url2, "</a>");
157               }
158               fprintf(hf, "<tr><td>%s%s%s<td align=center>%d%s%s<td align=center>%s\n\
159 ",
160                       url1,
161                       eggtable[i].name,
162                       url2,
163                       eggtable[i].id,
164                       ustime,
165                       udate,
166                       eggtable[i].setup ? "Yes" : "No"
167               );
168
169               if (eggStatistics[i].trials == 0) {
170                   fprintf(hf, "<td align=center>&nbsp;<td align=center>&nbsp;<td align=center>&nbsp;<td align=center>&nbsp;\n");
171               } else {
172                   uint32 coverage;
173
174                   /* The following expression computes "coverage" for
175                      the egg--the percentage of samples received
176                      to the number prescribed by the protocol
177                      for the time in which the egg has been in contact. */
178                   coverage = (100L * eggStatistics[i].trials * protocol.samp_rec) /
179                       (protocol.sec_rec * (now - eggStatistics[i].firstPacket));
180                   if (coverage > 100) {
181                       coverage = 100;
182                   }
183
184                   fprintf(hf, "<td align=center>%ld<td align=center>%ld<td align=center>%ld%%<td align=center>%.3f\n",
185                           eggStatistics[i].trials,
186                           eggStatistics[i].missing,
187                           coverage,
188                           eggStatistics[i].sum / eggStatistics[i].trials
189                   );
190               }
191             }
192
193
194             fprintf(hf, "\
195 </table>\n\
196 </center>\n\
197 <center>\n\
198 <h4>Egg Status Table Fields</h4>\n\
199 <table width=\"75%%\" cellpadding=3>\n\
200 <tr><td valign=top><b>Name</b> <td>Egg name<p>\n\
201 <tr><td valign=top><b>Number</b> <td>Egg number.  Thousands digit indicates type of random event generator.<p>\n\
202 <tr><td valign=top><b>Up Since</b> <td>Time and date in first packet received from egg since basket started.<p>\n\
203 <tr><td valign=top><b>Last Packet</b> <td>Time and date of most recently received packet from egg.<p>\n\
204 <tr><td valign=top><b>Active</b> <td>Has egg ever communicated with this basket since it was started?<p>\n\
205 <tr><td valign=top><b>Trials</b> <td>Number of trials received from this egg.<p>\n\
206 <tr><td valign=top><b>Missed</b> <td>Number of scheduled trials missed by egg due to synchronisation problems.<p>\n\
207 <tr><td valign=top><b>Coverage</b> <td>Percent of trials collected from this egg since basket\n\
208     started compared to the number scheduled by the protocol.<p>\n\
209 <tr><td valign=top><b>Mean</b> <td>Arithmetic mean of trials performed by this egg.\n\
210 </table>\n\
211 </center>\n\
212 </body>\n\
213 </html>\n");
214             fclose(hf);
215 #ifdef DEBUG
216             fprintf(stderr, "Updating HTML file %s at %s",
217                 htmlFile, asctime(gmtime((time_t *) &now)));
218 #endif
219         }
220     }
221 }
222
223 static void Usage(void) {
224   printf("Usage: %s [-i interface] [-p port]\n", pgmname);
225   exit(-1);
226 }
227
228 /*  Main program.  */
229
230 int main(int argc, char *argv[]) {
231   char                  *pktbuf;
232   uint16                pkttype, pktsize, pkt_eggid;
233   int                   sdlisten, res;
234   struct sockaddr_in    rhost;
235   ReqPacket             rpkt;
236   SettingsPacket        spkt;
237   int                   i, isbasket, isegg, thisegg;
238   char                  *remname;
239 #ifdef SIGACTION_WORKING
240   struct sigaction      sigact;
241 #endif
242 #ifdef EGG_DYNAMIC 
243   uint32                ipvalidate, ipmatch, ipmask;
244 #endif
245
246   pgmname = argv[0];
247   argv++; argc--;
248
249   /* Defaults correspond to original usage */
250   myport = BASKETPORT;
251   myaddr = NULL;
252
253   while (argc) {
254     if (argv[0][0] == '-') {
255       switch(argv[0][1]) {
256       case 'i': /* Specify interface */
257         if (argc < 2) Usage();
258         myaddr = argv[1];
259         argv++; argc--;
260         break;
261       case 'p': /* Specify port */
262         if (argc < 2) Usage();
263         myport = atoi(argv[1]);
264         if (myport < 0) Usage();
265         argv++; argc--;
266         break;
267       default:  /* Oops. */
268         Usage();
269       }
270     } else {
271       Usage();
272     }
273     argv++; argc--;
274   }
275
276   LoadRCFile();
277   LoadEggStats();
278
279 #ifdef Solaris
280   /* Solaris has an eccentric definition of sigaction() which
281      doesn't seem to even work according to Sun's own
282      documentation.  (sa_flags is a 4 element array of
283      unsigned longs, with no mention of how one stores the
284      flags into it.)  Let's just use plain old signal for
285      the moment. */
286   signal(SIGHUP, handle_sighup);
287 #else
288 #ifdef SIGACTION_WORKING
289   sigact.sa_handler = handle_sighup;
290   sigact.sa_mask = 0;
291   sigact.sa_flags = 0;
292   sigact.sa_restorer = NULL;
293   sigaction(SIGHUP, &sigact, NULL);
294 #else
295   signal(SIGHUP, handle_sighup);
296 #endif
297 #endif
298
299   /* No connection at outset */
300   sdlisten = -1;
301
302   /* Initialize storage system. */
303   InitStorage("%Y-%m/%Y-%m-%d-$b");
304
305   upsince = getzulutime(NULL);
306
307   fprintf(stderr, "Starting basket %s.\n", Version);
308
309   /* Inner loop is currently single thread:
310      1A. Wait for connection.  When a connection comes in,
311          packet has been decrypted and verified.
312      1B. Validate connection.
313      1C. Reply to connection. */
314   while (1) {
315     /* If no current listening connection, try to get one,
316        and if that fails, sleep and try later. */
317
318     if (sdlisten < 0) {
319       /* Get a listening socket at BASKETPORT.
320          Only allow our */
321       sdlisten = InitNetwork(myaddr, myport);
322       if (sdlisten < 0) {
323         sleep(1);
324         continue;
325       }
326     }
327
328     /* 1A. Wait for connection. */
329     res = NetListen(sdlisten, &pktbuf, &rhost, TRUE);
330     if (res < 0) {
331       fprintf(stderr, "NetListen error: %d\n", res);
332       continue;
333     }
334     
335     /* 1B. Validate connection. 
336        Remote host address is in rhost.  Make sure this is either an
337        egg or a basket, and set the flag appropriately. */
338 #ifdef EGG_DYNAMIC
339     ipvalidate = ntohl(rhost.sin_addr.s_addr);
340 #endif
341
342     isegg = isbasket = 0;
343     for (i = 0; i < numeggs; i++) {
344 #ifdef EGG_DYNAMIC
345       /* When mask == 32:       ipmask = 0xFFFFFFFF (all significant)
346               mask == 16:       ipmask = 0xFFFF0000 (only top half sig)
347               mask ==  0:       ipmask = 0x00000000 (none significant) */
348       ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
349       ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
350       if ((ipvalidate & ipmask) == (ipmatch & ipmask)) {
351         isegg = eggtable[i].id;
352         thisegg = i;
353         remname = eggtable[i].name;
354       }      
355 #else
356       if (!memcmp(&(rhost.sin_addr),
357                   &(eggtable[i].ipaddr.sin_addr),
358                   sizeof(rhost.sin_addr))) {
359         isegg = eggtable[i].id;
360         thisegg = i;
361         remname = eggtable[i].name;
362       }
363 #endif
364     }
365     for (i = 0; i < numbaskets; i++) {
366       if (!memcmp(&(rhost.sin_addr),
367                   &(baskettable[i].ipaddr.sin_addr),
368                   sizeof(rhost.sin_addr))) {
369         isbasket = 1;
370         remname = baskettable[i].name;
371       }
372     }
373
374     if (!isegg && !isbasket) {
375       fprintf(stderr, "Attempt to connect from unknown source: %s\n",
376               sockaddr2dquad(&rhost));
377       continue;
378     }
379
380     memcpy(&pkttype, pktbuf, sizeof(uint16));
381     memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
382     pkttype = ntohs(pkttype);
383     pktsize = ntohs(pktsize);
384     if (pktsize < 6) {
385       fprintf(stderr, "Packet doesn't contain an egg id, why?\n");
386       continue;
387     }
388     pkt_eggid = ntohs(*((uint16 *) (pktbuf + 4)));
389
390     /* Am I hearing from who I think I am? */
391 #if EGG_DYNAMIC
392     /* Have to assume the best case before testing: that the
393        pkt_eggid is actually correct. */
394     for (i = 0; i < numeggs; i++) if (eggtable[i].id == pkt_eggid) break;
395
396     if (i == numeggs) {
397       /* Must be a spoof, and possibly an attempt to crash software too. */
398       fprintf(stderr, "%s: Egg spoofing?  %s reporting itself as %d\n",
399               pgmname, hl2dquad(ipvalidate), pkt_eggid);
400       continue;
401     }
402
403     ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
404     ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
405     if ((ipvalidate & ipmask) != (ipmatch & ipmask)) {
406       fprintf(stderr, "%s: Egg spoofing?  %s reporting itself as %d\n",
407               pgmname, hl2dquad(ipvalidate), pkt_eggid);
408       continue;
409     } else {
410       isegg = eggtable[i].id;
411       thisegg = i;
412       remname = eggtable[i].name;
413     }      
414 #else
415     if (isegg != pkt_eggid) {
416       fprintf(stderr, "%s: Egg spoofing?  %d reporting itself as %d\n",
417               pgmname, isegg, pkt_eggid);
418       continue;
419     }
420 #endif
421
422     /* 1D. Reply to connection. */
423     switch(pkttype) {
424     case DATA_PACKET:
425       /* Put it away. */
426       BasketReceiveDataPacket(pktbuf, isegg, thisegg, &rhost);
427
428       break;
429     case REQ_PACKET:
430       /* Is this how baskets communicate? */
431       break;
432     case AWAKE_PACKET:
433 #ifdef PACKETDUMP
434       {
435         uint32 egg_now_time;
436
437         memcpy(&egg_now_time, pktbuf + 6, sizeof egg_now_time);
438         egg_now_time = ntohl(egg_now_time);
439         fprintf(stderr, "Awake egg %d (%s) at its %lu: %s",
440                 pkt_eggid, eggtable[thisegg].name, egg_now_time,
441                 asctime(gmtime((time_t *) &egg_now_time)));
442       }
443 #endif
444
445       /* Calculate last data received from this egg, decide what
446          to ask for. */
447       MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
448       rhost.sin_port = htons(EGGPORT);
449       res = NetTalk(&rhost, (char *)&rpkt, TRUE);
450       if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
451
452       /* If setup looks like it's out of date, correct it. */
453       if (!eggtable[thisegg].setup) {
454         MakeSettings(&spkt, thisegg);
455         rhost.sin_port = htons(EGGPORT);
456         res = NetTalk(&rhost, (char *)&spkt, TRUE);
457         if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
458       }
459       break;
460
461     case SETTINGS_PACKET:
462       /* I don't have settings, Bozo. */
463       break;
464     }
465     free(pktbuf);
466   }
467 }
468
469 /*  handle_sighup  --  Catch SIGHUP and reload configuraton files.  */
470
471 static void handle_sighup(int arg) {
472   /* Reread the RC file */
473   LoadRCFile();
474   LoadEggStats();
475 #ifdef DEBUG
476   fprintf(stderr, "Received SIGHUP: reloading configuration files.\n");
477 #endif
478 #ifndef SIGACTION_WORKING
479   signal(SIGHUP, handle_sighup);      /* Reset signal handler for bottom-feeder signal() */
480 #endif
481 }
482
483 /*  LoadRCFile  --  Load basket configuration file.  */
484
485 static void LoadRCFile(void) {
486   FILE *fp;
487   char linebuf[200];
488   char *myargv[MAX_PARSE], *tp;
489   int myargc, lcount, p, i;
490
491   if ((fp = fopen(".basketrc", "r")) == NULL) {
492     if ((fp = fopen("~/.basketrc", "r")) == NULL) {
493       if ((fp = fopen("/etc/basketrc", "r")) == NULL) {
494         fprintf(stderr, "%s: Couldn't find a basket RC file.\n", pgmname);
495         exit(-1);
496       }
497     }
498   }
499
500   numeggs = numbaskets = 0;
501   lcount = 0;
502
503   while(fgets(linebuf, 200, fp) != NULL) {
504     /* Comments and blank lines ignored. */
505     lcount++;
506     if (*linebuf == '#' || *linebuf == '\n') continue;
507     Parse(linebuf, &myargc, myargv);
508     if (myargc == 0) continue;
509     if (!strcmp(myargv[0], "EGG")) {
510       if (myargc < 7 || myargc > 8) {
511         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
512                 pgmname, lcount, myargv[0]);
513         exit(-1);
514       }
515         
516       eggtable[numeggs].name = mallocpy(myargv[1]);
517       eggtable[numeggs].id = atoi(myargv[2]);
518       dquad2sockaddr(&(eggtable[numeggs].ipaddr), 
519                      &(eggtable[numeggs].netmask),
520                      myargv[3]);
521       eggtable[numeggs].primbasket = mallocpy(myargv[4]);
522       eggtable[numeggs].conntype = (!strcmp(myargv[5], "PERM"))?CONN_PERM:CONN_DND;
523       eggtable[numeggs].connival = atoi(myargv[6]);
524       eggtable[numeggs].url = NULL;
525       if ((myargc > 7) && (strcmp(myargv[7], ".") != 0)) {
526         eggtable[numeggs].url = mallocpy(myargv[7]);
527       }
528       numeggs++;
529     } else if (!strcmp(myargv[0], "BASKET")) {
530       if (myargc != 3) {
531         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
532                 pgmname, lcount, myargv[0]);
533         exit(-1);
534       }
535       baskettable[numbaskets].name = mallocpy(myargv[1]);
536       dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
537       numbaskets++;
538     } else if (!strcmp(myargv[0], "PROTOCOL")) {
539       if (myargc != 5) {
540         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
541                 pgmname, lcount, myargv[0]);
542         exit(-1);
543       }
544       protocol.samp_rec = atoi(myargv[1]);
545       protocol.sec_rec = atoi(myargv[2]);
546       protocol.rec_pkt = atoi(myargv[3]);
547       protocol.trialsz = atoi(myargv[4]);
548     } else if (!strcmp(myargv[0], "HTML")) {
549       if (myargc != 3) {
550         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
551                 pgmname, lcount, myargv[0]);
552         exit(-1);
553       }
554       htmlInterval = atoi(myargv[1]);
555       strcpy(htmlFile, myargv[2]);
556 #ifdef DEBUG
557       fprintf(stderr, "Updating HTML file %s every %d seconds.\n", htmlFile, htmlInterval);
558 #endif
559     } else if (!strcmp(myargv[0], "PORT") ||
560                !strcmp(myargv[0], "INTERFACE")) {
561       if (myargc != 2) {
562         fprintf(stderr, "%s: RC error, line %d, poor %s format\n", 
563                 pgmname, lcount, myargv[0]);
564         exit(-1);
565       }
566       tp = (char *) malloc(200);
567       for (*tp = 0, p = 1; p < myargc; p++) {
568         strcat(tp, myargv[p]);
569         if (p < myargc-1) strcat(tp, " ");
570       }
571
572       if (!strcmp(myargv[0], "PORT")) {
573         if (atoi(tp) <= 0) {
574           fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
575                   pgmname, atoi(tp), myport);
576         } else {
577           myport = atoi(tp);
578         }
579         free(tp);
580       } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
581     } else {
582       fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[0]);
583       exit(-1);
584     }
585   }
586   fclose(fp);
587
588 #if REPORT > 0
589   /* Report a list of the eggs that we are accepting. */
590   for (i = 0; i < numeggs; i++) {
591     fprintf(stderr, "Egg %2d:\tid = %4d\tip=%s/%d\n", 
592             i, eggtable[i].id, 
593             sockaddr2dquad(&(eggtable[i].ipaddr)),
594             eggtable[i].netmask);
595   }
596 #endif
597 }
598
599 /*  BasketReceiveDataPacket  --  Process a data packet received frm an egg.  */
600
601 static int32 BasketReceiveDataPacket(char *pkt, int16 isegg, int16 thisegg,
602                               struct sockaddr_in *rhost) {
603   EggHeader             *dpktp;
604   EggCarton             result;
605   ReqPacket             rpkt;
606   uint16                offset, rec;
607   uint32                latest;
608   int32                 res;
609 #ifdef DEBUG
610   uint16                i;
611 #endif
612
613   EggHeader dpk;
614   char *pktP = pkt;
615   
616   /* Unpack the portable header into a host-order and aligned
617      EggHeader packet. */
618   
619   dpktp = &dpk;
620   unpackShort(dpk.type);
621   unpackShort(dpk.pktsize);
622   unpackShort(dpk.eggid);
623   unpackShort(dpk.samp_rec);
624   unpackShort(dpk.sec_rec);
625   unpackShort(dpk.rec_pkt);
626   unpackByte(dpk.trialsz);
627   unpackShort(dpk.numrec);
628
629   /* This spoofing test is probably completely redundant now. */
630 #ifndef EGG_DYNAMIC      
631   /* Am I hearing from an egg, and is it who I think it is? */
632   if (isegg && isegg != dpktp->eggid) {
633     fprintf(stderr, "%s: Egg spoofing?  %d reporting itself as %d\n",
634             pgmname, isegg, dpktp->eggid);
635     return -1;
636   }
637 #endif
638
639   /* Save the packet header in the result EggCarton buffer. */
640
641   memcpy(&(result.hdr), dpktp, sizeof(EggHeader));
642   offset = sizeof(EggHeader);
643
644 #ifdef DEBUG
645   {
646     uint32 stamp = 0;
647     char *spktp = pktP;
648
649     if (result.hdr.numrec > 0) {
650       unpackLong(stamp);
651     }
652     pktP = spktp;
653     fprintf(stderr, "Received packet: %d records from egg %d (%s).\n",
654       result.hdr.numrec, dpktp->eggid, eggtable[thisegg].name);
655   }
656 #endif
657
658   /* Unpack the individual data records from the packet and
659      transcribe to the result EggCarton.  */
660   
661   for (latest = 0, rec = 0; rec < result.hdr.numrec; rec++) {
662     unpackLong(result.records[rec].timestamp);   /* Record timestamp */
663     if (result.records[rec].timestamp > latest)
664       latest = result.records[rec].timestamp;
665 #ifdef DEBUG
666     fprintf(stderr, "  %10lu  ", result.records[rec].timestamp);
667 #endif
668     /* Assumes sizeof(trial) = 1 */
669     unpackBytes(&(result.records[rec].trials), result.hdr.samp_rec);
670 #ifdef DEBUG
671     for (i = 0; i < result.hdr.samp_rec; i++) {
672       fprintf(stderr, "%3d ", result.records[rec].trials[i]);
673     }
674     fprintf(stderr, "%s", asctime(gmtime((time_t *) &result.records[rec].timestamp)));
675 #endif
676   }
677
678   /* Save the packet in the basket database file. */
679   
680   if ((res = SavePacket(&result)) < 0) return res;
681
682   /* If we're generating an HTML report, update the per-basket
683      statistics based on the content of this packet. */
684
685   if (htmlReport) {
686       int r, t;
687       uint32 ptrials = 0, psum = 0, dropped = 0;
688
689       /* Update the egg status for HTML report. */
690
691       for (r = 0; r < result.hdr.rec_pkt; r++) {
692           for (t = 0; t < result.hdr.samp_rec; t++) {
693               if (result.records[r].trials[t] != EGG_MISSING_DATA) {
694                   ptrials++;
695                   psum += result.records[r].trials[t];
696               } else {
697                   dropped++;
698               }
699           }
700       }
701       eggStatistics[thisegg].trials += ptrials;
702       eggStatistics[thisegg].sum += psum;
703
704       /* If this is the first packet received from the egg, and
705          the first sample in it is missing, don't count missing
706          samples against this egg, since it's probable this is
707          the first packet collected since the egg started, which
708          will contain initial missing samples for time before the
709          egg started. */
710
711       if (eggStatistics[thisegg].firstPacket != 0) {
712           eggStatistics[thisegg].missing += dropped;
713       } else {
714           eggStatistics[thisegg].firstPacket = result.records[0].timestamp;
715       }
716   }
717
718   if (latest > eggtable[thisegg].lastupd) {
719     eggtable[thisegg].lastupd = latest;
720     SaveEggStats();
721     updateHTML();                     /* Update HTML status file, if warranted */
722     if (latest < getzulutime(NULL)) {
723       /* Get more data if it looks like there should be more. */
724       MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
725       rhost->sin_port = htons(EGGPORT);
726       res = NetTalk(rhost, (char *)&rpkt, TRUE);
727       if (res < 0) fprintf(stderr, "NetTalk error %d.\n", (int)res);
728     }
729   }
730
731   /* If the protocol now in effect differs from that of the
732      packet just received from the egg, mark the egg as not
733      set-up, and thus in need of a SETTINGS_PACKET to reset
734      its protocol. */
735
736   eggtable[thisegg].setup = (protocol.samp_rec == dpktp->samp_rec &&
737                              protocol.sec_rec == dpktp->sec_rec &&
738                              protocol.rec_pkt == dpktp->rec_pkt &&
739                              protocol.trialsz == dpktp->trialsz);
740
741   return ERR_NONE;
742 }
743
744 /*  MakeRequest  --  Assemble a request packet for a given egg,
745                      specifying, in whence, the time and date
746                      of the last sample received from that egg.  */
747
748 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence) {
749   char *pktP = (char *) pkt;
750
751   packShort(REQ_PACKET);
752   packShort((4 * sizeof(uint16)) + sizeof(uint32));
753   packShort(eggid);
754   packLong(whence);
755 }
756
757 /*  MakeSettings  --  Create a settings packet.  This packet,
758                       sent periodically to connected eggs,
759                       informs them of any change in protocol.  */
760
761 static void MakeSettings(SettingsPacket *pkt, uint16 eggid) {
762   char *pktP = (char *) pkt;
763
764   packShort(SETTINGS_PACKET);
765   packShort((7 * sizeof(uint16)) + sizeof(uint32) + sizeof(trial));
766   packShort(eggid);
767   packLong(getzulutime(NULL));
768   packShort(protocol.samp_rec);
769   packShort(protocol.sec_rec);
770   packShort(protocol.rec_pkt);
771   packByte(protocol.trialsz);
772 }
773
774 /*  LoadEggStats  --  Initialise in-memory egg status table
775                       from the EGGSTATS file.  */
776
777 static void LoadEggStats(void) {
778   FILE *fp;
779   char linebuf[200], *myargv[MAX_PARSE], *name;
780   int i, f, myargc, id, setup;
781   int32 lastupd;
782
783   /* Default stats */
784   for (i = 0; i < numeggs; i++) {
785     eggtable[i].lastupd = 0;
786     eggtable[i].setup = 0;
787   }
788   
789   if ((fp = fopen(EGGSTATS, "r")) == NULL) return;
790   
791   while(fgets(linebuf, 200, fp) != NULL) {
792     /* Comments and blank lines ignored. */
793     if (*linebuf == '#' || *linebuf == '\n') continue;
794     Parse(linebuf, &myargc, myargv);
795     if (myargc == 0) continue;
796     if (!strcmp(myargv[0], "STAT") && myargc == 5) {
797       name = myargv[1];
798       id = atoi(myargv[2]);
799       lastupd = atol(myargv[3]);
800       setup = atoi(myargv[4]);
801       for (f = 0, i = 0; i < numeggs; i++) {
802         if (eggtable[i].id == id &&
803             !strcmp(eggtable[i].name, name)) {
804           eggtable[i].lastupd = lastupd;
805           eggtable[i].setup = setup;
806           f = 1;
807         }
808       }
809       if (!f) fprintf(stderr, "%s: Egg '%s' no longer hosted.\n", pgmname, name);
810     }
811   }
812   fclose(fp);
813 }
814
815 /*  SaveEggStats  --  Dump in-memory egg status to the EGGSTATS file.  */
816
817 static void SaveEggStats(void) {
818   FILE *fp;
819   int i;
820
821   if ((fp = fopen(EGGSTATS, "w")) == NULL) {
822     /* Couldn't write stats! */  
823     fprintf(stderr, "%s: Couldn't save egg stats!\n", pgmname);
824     return;
825   }
826
827   fprintf(fp, "# Status lines of form:\n");
828   fprintf(fp, "#   STAT <eggname> <eggid> <lastupdzulu> <setupvalid>\n");
829   for (i = 0; i < numeggs; i++) {
830     fprintf(fp, "STAT %s %d %ld %d\n",
831             eggtable[i].name,
832             eggtable[i].id,
833             eggtable[i].lastupd,
834             eggtable[i].setup);
835   }
836   fclose(fp);
837 }