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