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>
52 #define EGGSTATS "egg.status"
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);
63 EggEntry eggtable[MAX_EGGS];
64 BasketEntry baskettable[MAX_BASKETS];
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 */
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 ? */
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];
84 /* updateHTML -- Update HTML status file if a decent interval
85 has passed since the last update. */
87 static void updateHTML(void)
91 static uint32 lastHtml = 0;
92 uint32 now = getzulutime(NULL);
93 char udate[256], ustime[256];
94 static char timeFormat[] = "%Y-%m-%d %T";
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. */
106 if ((now - lastHtml) >= htmlInterval) {
110 hf = fopen(htmlFile, "w");
112 fprintf(stderr, "Cannot open HTML status file %s. Updates discarded.\n", htmlFile);
113 htmlFile[0] = 0; /* Suppress further updates */
117 strftime(udate, sizeof udate, timeFormat, gmtime((time_t *) &now));
118 strftime(ustime, sizeof ustime, timeFormat, gmtime((time_t *) &upsince));
121 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n\
122 <html version=\"-//W3C//DTD HTML 3.2 Final//EN\">\n\
124 <title>Basket %s Status Report</title>\n\
125 <meta http-equiv=\"Refresh\" content=\"%d\">\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\
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\
140 baskettable[0].name, htmlInterval, baskettable[0].name, udate, ustime, Version);
142 for (i = 0; i < numeggs; i++) {
143 char url1[256], url2[256];
145 if ((eggtable[i].lastupd == 0) || (eggStatistics[i].firstPacket == 0)) {
146 strcpy(udate, "<td colspan=2 align=center><em>Never contacted</em>");
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));
152 if (eggtable[i].url == NULL) {
153 url1[0] = url2[0] = 0;
155 sprintf(url1, "<a href=\"%s\">", eggtable[i].url);
156 strcpy(url2, "</a>");
158 fprintf(hf, "<tr><td>%s%s%s<td align=center>%d%s%s<td align=center>%s\n\
166 eggtable[i].setup ? "Yes" : "No"
169 if (eggStatistics[i].trials == 0) {
170 fprintf(hf, "<td align=center> <td align=center> <td align=center> <td align=center> \n");
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) {
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,
188 eggStatistics[i].sum / eggStatistics[i].trials
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\
216 fprintf(stderr, "Updating HTML file %s at %s",
217 htmlFile, asctime(gmtime((time_t *) &now)));
223 static void Usage(void) {
224 printf("Usage: %s [-i interface] [-p port]\n", pgmname);
230 int main(int argc, char *argv[]) {
232 uint16 pkttype, pktsize, pkt_eggid;
234 struct sockaddr_in rhost;
237 int i, isbasket, isegg, thisegg;
239 #ifdef SIGACTION_WORKING
240 struct sigaction sigact;
243 uint32 ipvalidate, ipmatch, ipmask;
249 /* Defaults correspond to original usage */
254 if (argv[0][0] == '-') {
256 case 'i': /* Specify interface */
257 if (argc < 2) Usage();
261 case 'p': /* Specify port */
262 if (argc < 2) Usage();
263 myport = atoi(argv[1]);
264 if (myport < 0) Usage();
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
286 signal(SIGHUP, handle_sighup);
288 #ifdef SIGACTION_WORKING
289 sigact.sa_handler = handle_sighup;
292 sigact.sa_restorer = NULL;
293 sigaction(SIGHUP, &sigact, NULL);
295 signal(SIGHUP, handle_sighup);
299 /* No connection at outset */
302 /* Initialize storage system. */
303 InitStorage("%Y-%m/%Y-%m-%d-$b");
305 upsince = getzulutime(NULL);
307 fprintf(stderr, "Starting basket %s.\n", Version);
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. */
315 /* If no current listening connection, try to get one,
316 and if that fails, sleep and try later. */
319 /* Get a listening socket at BASKETPORT.
321 sdlisten = InitNetwork(myaddr, myport);
328 /* 1A. Wait for connection. */
329 res = NetListen(sdlisten, &pktbuf, &rhost, TRUE);
331 fprintf(stderr, "NetListen error: %d\n", res);
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. */
339 ipvalidate = ntohl(rhost.sin_addr.s_addr);
342 isegg = isbasket = 0;
343 for (i = 0; i < numeggs; i++) {
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;
353 remname = eggtable[i].name;
356 if (!memcmp(&(rhost.sin_addr),
357 &(eggtable[i].ipaddr.sin_addr),
358 sizeof(rhost.sin_addr))) {
359 isegg = eggtable[i].id;
361 remname = eggtable[i].name;
365 for (i = 0; i < numbaskets; i++) {
366 if (!memcmp(&(rhost.sin_addr),
367 &(baskettable[i].ipaddr.sin_addr),
368 sizeof(rhost.sin_addr))) {
370 remname = baskettable[i].name;
374 if (!isegg && !isbasket) {
375 fprintf(stderr, "Attempt to connect from unknown source: %s\n",
376 sockaddr2dquad(&rhost));
380 memcpy(&pkttype, pktbuf, sizeof(uint16));
381 memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
382 pkttype = ntohs(pkttype);
383 pktsize = ntohs(pktsize);
385 fprintf(stderr, "Packet doesn't contain an egg id, why?\n");
388 pkt_eggid = ntohs(*((uint16 *) (pktbuf + 4)));
390 /* Am I hearing from who I think I am? */
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;
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);
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);
410 isegg = eggtable[i].id;
412 remname = eggtable[i].name;
415 if (isegg != pkt_eggid) {
416 fprintf(stderr, "%s: Egg spoofing? %d reporting itself as %d\n",
417 pgmname, isegg, pkt_eggid);
422 /* 1D. Reply to connection. */
426 BasketReceiveDataPacket(pktbuf, isegg, thisegg, &rhost);
430 /* Is this how baskets communicate? */
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)));
445 /* Calculate last data received from this egg, decide what
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);
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);
461 case SETTINGS_PACKET:
462 /* I don't have settings, Bozo. */
469 /* handle_sighup -- Catch SIGHUP and reload configuraton files. */
471 static void handle_sighup(int arg) {
472 /* Reread the RC file */
476 fprintf(stderr, "Received SIGHUP: reloading configuration files.\n");
478 #ifndef SIGACTION_WORKING
479 signal(SIGHUP, handle_sighup); /* Reset signal handler for bottom-feeder signal() */
483 /* LoadRCFile -- Load basket configuration file. */
485 static void LoadRCFile(void) {
488 char *myargv[MAX_PARSE], *tp;
489 int myargc, lcount, p, i;
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);
500 numeggs = numbaskets = 0;
503 while(fgets(linebuf, 200, fp) != NULL) {
504 /* Comments and blank lines ignored. */
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]);
516 eggtable[numeggs].name = mallocpy(myargv[1]);
517 eggtable[numeggs].id = atoi(myargv[2]);
518 dquad2sockaddr(&(eggtable[numeggs].ipaddr),
519 &(eggtable[numeggs].netmask),
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]);
529 } else if (!strcmp(myargv[0], "BASKET")) {
531 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
532 pgmname, lcount, myargv[0]);
535 baskettable[numbaskets].name = mallocpy(myargv[1]);
536 dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
538 } else if (!strcmp(myargv[0], "PROTOCOL")) {
540 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
541 pgmname, lcount, myargv[0]);
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")) {
550 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
551 pgmname, lcount, myargv[0]);
554 htmlInterval = atoi(myargv[1]);
555 strcpy(htmlFile, myargv[2]);
557 fprintf(stderr, "Updating HTML file %s every %d seconds.\n", htmlFile, htmlInterval);
559 } else if (!strcmp(myargv[0], "PORT") ||
560 !strcmp(myargv[0], "INTERFACE")) {
562 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
563 pgmname, lcount, myargv[0]);
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, " ");
572 if (!strcmp(myargv[0], "PORT")) {
574 fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
575 pgmname, atoi(tp), myport);
580 } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
582 fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[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",
593 sockaddr2dquad(&(eggtable[i].ipaddr)),
594 eggtable[i].netmask);
599 /* BasketReceiveDataPacket -- Process a data packet received frm an egg. */
601 static int32 BasketReceiveDataPacket(char *pkt, int16 isegg, int16 thisegg,
602 struct sockaddr_in *rhost) {
616 /* Unpack the portable header into a host-order and aligned
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);
629 /* This spoofing test is probably completely redundant now. */
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);
639 /* Save the packet header in the result EggCarton buffer. */
641 memcpy(&(result.hdr), dpktp, sizeof(EggHeader));
642 offset = sizeof(EggHeader);
649 if (result.hdr.numrec > 0) {
653 fprintf(stderr, "Received packet: %d records from egg %d (%s).\n",
654 result.hdr.numrec, dpktp->eggid, eggtable[thisegg].name);
658 /* Unpack the individual data records from the packet and
659 transcribe to the result EggCarton. */
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;
666 fprintf(stderr, " %10lu ", result.records[rec].timestamp);
668 /* Assumes sizeof(trial) = 1 */
669 unpackBytes(&(result.records[rec].trials), result.hdr.samp_rec);
671 for (i = 0; i < result.hdr.samp_rec; i++) {
672 fprintf(stderr, "%3d ", result.records[rec].trials[i]);
674 fprintf(stderr, "%s", asctime(gmtime((time_t *) &result.records[rec].timestamp)));
678 /* Save the packet in the basket database file. */
680 if ((res = SavePacket(&result)) < 0) return res;
682 /* If we're generating an HTML report, update the per-basket
683 statistics based on the content of this packet. */
687 uint32 ptrials = 0, psum = 0, dropped = 0;
689 /* Update the egg status for HTML report. */
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) {
695 psum += result.records[r].trials[t];
701 eggStatistics[thisegg].trials += ptrials;
702 eggStatistics[thisegg].sum += psum;
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
711 if (eggStatistics[thisegg].firstPacket != 0) {
712 eggStatistics[thisegg].missing += dropped;
714 eggStatistics[thisegg].firstPacket = result.records[0].timestamp;
718 if (latest > eggtable[thisegg].lastupd) {
719 eggtable[thisegg].lastupd = latest;
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);
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
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);
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. */
748 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence) {
749 char *pktP = (char *) pkt;
751 packShort(REQ_PACKET);
752 packShort((4 * sizeof(uint16)) + sizeof(uint32));
757 /* MakeSettings -- Create a settings packet. This packet,
758 sent periodically to connected eggs,
759 informs them of any change in protocol. */
761 static void MakeSettings(SettingsPacket *pkt, uint16 eggid) {
762 char *pktP = (char *) pkt;
764 packShort(SETTINGS_PACKET);
765 packShort((7 * sizeof(uint16)) + sizeof(uint32) + sizeof(trial));
767 packLong(getzulutime(NULL));
768 packShort(protocol.samp_rec);
769 packShort(protocol.sec_rec);
770 packShort(protocol.rec_pkt);
771 packByte(protocol.trialsz);
774 /* LoadEggStats -- Initialise in-memory egg status table
775 from the EGGSTATS file. */
777 static void LoadEggStats(void) {
779 char linebuf[200], *myargv[MAX_PARSE], *name;
780 int i, f, myargc, id, setup;
784 for (i = 0; i < numeggs; i++) {
785 eggtable[i].lastupd = 0;
786 eggtable[i].setup = 0;
789 if ((fp = fopen(EGGSTATS, "r")) == NULL) return;
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) {
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;
809 if (!f) fprintf(stderr, "%s: Egg '%s' no longer hosted.\n", pgmname, name);
815 /* SaveEggStats -- Dump in-memory egg status to the EGGSTATS file. */
817 static void SaveEggStats(void) {
821 if ((fp = fopen(EGGSTATS, "w")) == NULL) {
822 /* Couldn't write stats! */
823 fprintf(stderr, "%s: Couldn't save egg stats!\n", pgmname);
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",