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
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.
15 * Revision 1.7 1999/01/01 23:52:31 ghn
16 * Allow basket to start with no net connection and handle connection
19 * Revision 1.6 1998/12/31 22:07:56 ghn
20 * Rev 5 code: includes multi-reg support, HTML, etc.
22 * Revision 1.5 1998/08/03 20:40:13 kelvin
23 * PACKETDUMP, SIGHUP reset when signal caught.
25 * Revision 1.4 1998/08/01 18:52:49 ghn
26 * Added John's byte-order-independence changes.
28 * Revision 1.3 1998/08/01 17:02:05 ghn
29 * Incorporate John's Solaris fixes and terminate STAT lines properly.
31 * Revision 1.2 1998/07/21 11:44:04 ghn
34 * Copyright 1998 - Greg Nelson
41 #include <sys/types.h>
42 #include <sys/socket.h>
44 #include <netinet/in.h>
53 #define EGGSTATS "egg.status"
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);
64 EggEntry eggtable[MAX_EGGS];
65 BasketEntry baskettable[MAX_BASKETS];
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 */
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 ? */
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];
85 /* updateHTML -- Update HTML status file if a decent interval
86 has passed since the last update. */
88 static void updateHTML(void)
92 static uint32 lastHtml = 0;
93 uint32 now = getzulutime(NULL);
95 char udate[256], ustime[256];
96 static char timeFormat[] = "%Y-%m-%d %T";
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. */
108 if ((now - lastHtml) >= htmlInterval) {
112 hf = fopen(htmlFile, "w");
114 fprintf(stderr, "Cannot open HTML status file %s. Updates discarded.\n", htmlFile);
115 htmlFile[0] = 0; /* Suppress further updates */
120 strftime(udate, sizeof udate, timeFormat, gmtime(&now_val));
121 time_t upsince_val = upsince;
122 strftime(ustime, sizeof ustime, timeFormat, gmtime(&upsince_val));
125 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n\
126 <html version=\"-//W3C//DTD HTML 3.2 Final//EN\">\n\
128 <title>Basket %s Status Report</title>\n\
129 <meta http-equiv=\"Refresh\" content=\"%d\">\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\
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\
144 baskettable[0].name, htmlInterval, baskettable[0].name, udate, ustime, Version);
146 for (i = 0; i < numeggs; i++) {
147 char url1[256], url2[256];
149 if ((eggtable[i].lastupd == 0) || (eggStatistics[i].firstPacket == 0)) {
150 strcpy(udate, "<td colspan=2 align=center><em>Never contacted</em>");
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));
158 if (eggtable[i].url == NULL) {
159 url1[0] = url2[0] = 0;
161 sprintf(url1, "<a href=\"%s\">", eggtable[i].url);
162 strcpy(url2, "</a>");
164 fprintf(hf, "<tr><td>%s%s%s<td align=center>%d%s%s<td align=center>%s\n\
172 eggtable[i].setup ? "Yes" : "No"
175 if (eggStatistics[i].trials == 0) {
176 fprintf(hf, "<td align=center> <td align=center> <td align=center> <td align=center> \n");
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) {
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,
194 eggStatistics[i].sum / eggStatistics[i].trials
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\
223 fprintf(stderr, "Updating HTML file %s at %s",
224 htmlFile, asctime(gmtime(&now_val)));
230 static void Usage(void) {
231 printf("Usage: %s [-i interface] [-p port]\n", pgmname);
237 int main(int argc, char *argv[]) {
239 uint16 pkttype, pktsize, pkt_eggid;
241 struct sockaddr_in rhost;
244 int i, isbasket, isegg, thisegg;
246 #ifdef SIGACTION_WORKING
247 struct sigaction sigact;
250 uint32 ipvalidate, ipmatch, ipmask;
256 /* Defaults correspond to original usage */
261 if (argv[0][0] == '-') {
263 case 'i': /* Specify interface */
264 if (argc < 2) Usage();
268 case 'p': /* Specify port */
269 if (argc < 2) Usage();
270 myport = atoi(argv[1]);
271 if (myport < 0) Usage();
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
293 signal(SIGHUP, handle_sighup);
295 #ifdef SIGACTION_WORKING
296 sigact.sa_handler = handle_sighup;
299 sigact.sa_restorer = NULL;
300 sigaction(SIGHUP, &sigact, NULL);
302 signal(SIGHUP, handle_sighup);
306 /* No connection at outset */
309 /* Initialize storage system. */
310 InitStorage("%Y-%m/%Y-%m-%d-$b");
312 upsince = getzulutime(NULL);
314 fprintf(stderr, "Starting basket %s.\n", Version);
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. */
322 /* If no current listening connection, try to get one,
323 and if that fails, sleep and try later. */
326 /* Get a listening socket at BASKETPORT.
328 sdlisten = InitNetwork(myaddr, myport);
335 /* 1A. Wait for connection. */
336 res = NetListen(sdlisten, &pktbuf, &rhost, TRUE);
338 fprintf(stderr, "NetListen error: %d\n", res);
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. */
346 ipvalidate = ntohl(rhost.sin_addr.s_addr);
349 isegg = isbasket = 0;
350 for (i = 0; i < numeggs; i++) {
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;
360 remname = eggtable[i].name;
363 if (!memcmp(&(rhost.sin_addr),
364 &(eggtable[i].ipaddr.sin_addr),
365 sizeof(rhost.sin_addr))) {
366 isegg = eggtable[i].id;
368 remname = eggtable[i].name;
372 for (i = 0; i < numbaskets; i++) {
373 if (!memcmp(&(rhost.sin_addr),
374 &(baskettable[i].ipaddr.sin_addr),
375 sizeof(rhost.sin_addr))) {
377 remname = baskettable[i].name;
381 if (!isegg && !isbasket) {
382 fprintf(stderr, "Attempt to connect from unknown source: %s\n",
383 sockaddr2dquad(&rhost));
387 memcpy(&pkttype, pktbuf, sizeof(uint16));
388 memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
389 pkttype = ntohs(pkttype);
390 pktsize = ntohs(pktsize);
392 fprintf(stderr, "Packet doesn't contain an egg id, why?\n");
395 pkt_eggid = ntohs(*((uint16 *) (pktbuf + 4)));
397 /* Am I hearing from who I think I am? */
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;
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);
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);
417 isegg = eggtable[i].id;
419 remname = eggtable[i].name;
422 if (isegg != pkt_eggid) {
423 fprintf(stderr, "%s: Egg spoofing? %d reporting itself as %d\n",
424 pgmname, isegg, pkt_eggid);
429 /* 1D. Reply to connection. */
433 BasketReceiveDataPacket(pktbuf, isegg, thisegg, &rhost);
437 /* Is this how baskets communicate? */
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)));
453 /* Calculate last data received from this egg, decide what
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);
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);
469 case SETTINGS_PACKET:
470 /* I don't have settings, Bozo. */
477 /* handle_sighup -- Catch SIGHUP and reload configuraton files. */
479 static void handle_sighup(int arg) {
480 /* Reread the RC file */
484 fprintf(stderr, "Received SIGHUP: reloading configuration files.\n");
486 #ifndef SIGACTION_WORKING
487 signal(SIGHUP, handle_sighup); /* Reset signal handler for bottom-feeder signal() */
491 /* LoadRCFile -- Load basket configuration file. */
493 static void LoadRCFile(void) {
496 char *myargv[MAX_PARSE], *tp;
497 int myargc, lcount, p, i;
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);
508 numeggs = numbaskets = 0;
511 while(fgets(linebuf, 200, fp) != NULL) {
512 /* Comments and blank lines ignored. */
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]);
524 eggtable[numeggs].name = mallocpy(myargv[1]);
525 eggtable[numeggs].id = atoi(myargv[2]);
526 dquad2sockaddr(&(eggtable[numeggs].ipaddr),
527 &(eggtable[numeggs].netmask),
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]);
537 } else if (!strcmp(myargv[0], "BASKET")) {
539 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
540 pgmname, lcount, myargv[0]);
543 baskettable[numbaskets].name = mallocpy(myargv[1]);
544 dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
546 } else if (!strcmp(myargv[0], "PROTOCOL")) {
548 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
549 pgmname, lcount, myargv[0]);
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")) {
558 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
559 pgmname, lcount, myargv[0]);
562 htmlInterval = atoi(myargv[1]);
563 strcpy(htmlFile, myargv[2]);
565 fprintf(stderr, "Updating HTML file %s every %d seconds.\n", htmlFile, htmlInterval);
567 } else if (!strcmp(myargv[0], "PORT") ||
568 !strcmp(myargv[0], "INTERFACE")) {
570 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
571 pgmname, lcount, myargv[0]);
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, " ");
580 if (!strcmp(myargv[0], "PORT")) {
582 fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
583 pgmname, atoi(tp), myport);
588 } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
590 fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[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",
601 sockaddr2dquad(&(eggtable[i].ipaddr)),
602 eggtable[i].netmask);
607 /* BasketReceiveDataPacket -- Process a data packet received frm an egg. */
609 static int32 BasketReceiveDataPacket(char *pkt, int16 isegg, int16 thisegg,
610 struct sockaddr_in *rhost) {
624 /* Unpack the portable header into a host-order and aligned
629 unpack16(dpk.pktsize);
631 unpack16(dpk.samp_rec);
632 unpack16(dpk.sec_rec);
633 unpack16(dpk.rec_pkt);
634 unpack8(dpk.trialsz);
635 unpack16(dpk.numrec);
637 /* This spoofing test is probably completely redundant now. */
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);
647 /* Save the packet header in the result EggCarton buffer. */
649 memcpy(&(result.hdr), dpktp, sizeof(EggHeader));
650 offset = sizeof(EggHeader);
657 if (result.hdr.numrec > 0) {
661 fprintf(stderr, "Received packet: %d records from egg %d (%s).\n",
662 result.hdr.numrec, dpktp->eggid, eggtable[thisegg].name);
666 /* Unpack the individual data records from the packet and
667 transcribe to the result EggCarton. */
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;
674 fprintf(stderr, " %10u ", result.records[rec].timestamp);
676 /* Assumes sizeof(trial) = 1 */
677 unpack8s(&(result.records[rec].trials), result.hdr.samp_rec);
679 for (i = 0; i < result.hdr.samp_rec; i++) {
680 fprintf(stderr, "%3d ", result.records[rec].trials[i]);
682 time_t timestamp_val = result.records[rec].timestamp;
683 fprintf(stderr, "%s", asctime(gmtime(×tamp_val)));
687 /* Save the packet in the basket database file. */
689 if ((res = SavePacket(&result)) < 0) return res;
691 /* If we're generating an HTML report, update the per-basket
692 statistics based on the content of this packet. */
696 uint32 ptrials = 0, psum = 0, dropped = 0;
698 /* Update the egg status for HTML report. */
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) {
704 psum += result.records[r].trials[t];
710 eggStatistics[thisegg].trials += ptrials;
711 eggStatistics[thisegg].sum += psum;
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
720 if (eggStatistics[thisegg].firstPacket != 0) {
721 eggStatistics[thisegg].missing += dropped;
723 eggStatistics[thisegg].firstPacket = result.records[0].timestamp;
727 if (latest > eggtable[thisegg].lastupd) {
728 eggtable[thisegg].lastupd = latest;
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);
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
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);
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. */
757 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence) {
758 char *pktP = (char *) pkt;
761 pack16((4 * sizeof(uint16)) + sizeof(uint32));
766 /* MakeSettings -- Create a settings packet. This packet,
767 sent periodically to connected eggs,
768 informs them of any change in protocol. */
770 static void MakeSettings(SettingsPacket *pkt, uint16 eggid) {
771 char *pktP = (char *) pkt;
773 pack16(SETTINGS_PACKET);
774 pack16((7 * sizeof(uint16)) + sizeof(uint32) + sizeof(trial));
776 pack32(getzulutime(NULL));
777 pack16(protocol.samp_rec);
778 pack16(protocol.sec_rec);
779 pack16(protocol.rec_pkt);
780 pack8(protocol.trialsz);
783 /* LoadEggStats -- Initialise in-memory egg status table
784 from the EGGSTATS file. */
786 static void LoadEggStats(void) {
788 char linebuf[200], *myargv[MAX_PARSE], *name;
789 int i, f, myargc, id, setup;
793 for (i = 0; i < numeggs; i++) {
794 eggtable[i].lastupd = 0;
795 eggtable[i].setup = 0;
798 if ((fp = fopen(EGGSTATS, "r")) == NULL) return;
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) {
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;
818 if (!f) fprintf(stderr, "%s: Egg '%s' no longer hosted.\n", pgmname, name);
824 /* SaveEggStats -- Dump in-memory egg status to the EGGSTATS file. */
826 static void SaveEggStats(void) {
830 if ((fp = fopen(EGGSTATS, "w")) == NULL) {
831 /* Couldn't write stats! */
832 fprintf(stderr, "%s: Couldn't save egg stats!\n", pgmname);
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",