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);
94 char udate[256], ustime[256];
95 static char timeFormat[] = "%Y-%m-%d %T";
97 /* When first called, set the last HTML update interval to
98 the present moment. This defers generation of the first
99 HTML report until htmlInterval elapses. This prevents
100 issuing a potentially-misleading HTML report based on
101 incomplete information. */
107 if ((now - lastHtml) >= htmlInterval) {
111 hf = fopen(htmlFile, "w");
113 fprintf(stderr, "Cannot open HTML status file %s. Updates discarded.\n", htmlFile);
114 htmlFile[0] = 0; /* Suppress further updates */
118 strftime(udate, sizeof udate, timeFormat, gmtime((time_t *) &now));
119 strftime(ustime, sizeof ustime, timeFormat, gmtime((time_t *) &upsince));
122 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n\
123 <html version=\"-//W3C//DTD HTML 3.2 Final//EN\">\n\
125 <title>Basket %s Status Report</title>\n\
126 <meta http-equiv=\"Refresh\" content=\"%d\">\n\
130 <h1>Basket %s Status Report</h1>\n\
131 <h3>Last update: %s UTC</h3>\n\
132 <h4>Basket started at %s UTC<br>\n\
136 <h2>Egg Status</h2>\n\
137 <table border cellpadding=4>\n\
138 <tr><th>Name<th>Number<th>Up Since<th>Last Packet<th>Active\n\
139 <th>Trials<th>Missed<th>Coverage<th>Mean\n\
141 baskettable[0].name, htmlInterval, baskettable[0].name, udate, ustime, Version);
143 for (i = 0; i < numeggs; i++) {
144 char url1[256], url2[256];
146 if ((eggtable[i].lastupd == 0) || (eggStatistics[i].firstPacket == 0)) {
147 strcpy(udate, "<td colspan=2 align=center><em>Never contacted</em>");
150 strftime(udate, sizeof udate, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggtable[i].lastupd));
151 strftime(ustime, sizeof ustime, "<td align=center>%Y-%m-%d %T", gmtime((time_t *) &eggStatistics[i].firstPacket));
153 if (eggtable[i].url == NULL) {
154 url1[0] = url2[0] = 0;
156 sprintf(url1, "<a href=\"%s\">", eggtable[i].url);
157 strcpy(url2, "</a>");
159 fprintf(hf, "<tr><td>%s%s%s<td align=center>%d%s%s<td align=center>%s\n\
167 eggtable[i].setup ? "Yes" : "No"
170 if (eggStatistics[i].trials == 0) {
171 fprintf(hf, "<td align=center> <td align=center> <td align=center> <td align=center> \n");
175 /* The following expression computes "coverage" for
176 the egg--the percentage of samples received
177 to the number prescribed by the protocol
178 for the time in which the egg has been in contact. */
179 coverage = (100L * eggStatistics[i].trials * protocol.samp_rec) /
180 (protocol.sec_rec * (now - eggStatistics[i].firstPacket));
181 if (coverage > 100) {
185 fprintf(hf, "<td align=center>%ld<td align=center>%ld<td align=center>%ld%%<td align=center>%.3f\n",
186 eggStatistics[i].trials,
187 eggStatistics[i].missing,
189 eggStatistics[i].sum / eggStatistics[i].trials
199 <h4>Egg Status Table Fields</h4>\n\
200 <table width=\"75%%\" cellpadding=3>\n\
201 <tr><td valign=top><b>Name</b> <td>Egg name<p>\n\
202 <tr><td valign=top><b>Number</b> <td>Egg number. Thousands digit indicates type of random event generator.<p>\n\
203 <tr><td valign=top><b>Up Since</b> <td>Time and date in first packet received from egg since basket started.<p>\n\
204 <tr><td valign=top><b>Last Packet</b> <td>Time and date of most recently received packet from egg.<p>\n\
205 <tr><td valign=top><b>Active</b> <td>Has egg ever communicated with this basket since it was started?<p>\n\
206 <tr><td valign=top><b>Trials</b> <td>Number of trials received from this egg.<p>\n\
207 <tr><td valign=top><b>Missed</b> <td>Number of scheduled trials missed by egg due to synchronisation problems.<p>\n\
208 <tr><td valign=top><b>Coverage</b> <td>Percent of trials collected from this egg since basket\n\
209 started compared to the number scheduled by the protocol.<p>\n\
210 <tr><td valign=top><b>Mean</b> <td>Arithmetic mean of trials performed by this egg.\n\
217 fprintf(stderr, "Updating HTML file %s at %s",
218 htmlFile, asctime(gmtime((time_t *) &now)));
224 static void Usage(void) {
225 printf("Usage: %s [-i interface] [-p port]\n", pgmname);
231 int main(int argc, char *argv[]) {
233 uint16 pkttype, pktsize, pkt_eggid;
235 struct sockaddr_in rhost;
238 int i, isbasket, isegg, thisegg;
240 #ifdef SIGACTION_WORKING
241 struct sigaction sigact;
244 uint32 ipvalidate, ipmatch, ipmask;
250 /* Defaults correspond to original usage */
255 if (argv[0][0] == '-') {
257 case 'i': /* Specify interface */
258 if (argc < 2) Usage();
262 case 'p': /* Specify port */
263 if (argc < 2) Usage();
264 myport = atoi(argv[1]);
265 if (myport < 0) Usage();
281 /* Solaris has an eccentric definition of sigaction() which
282 doesn't seem to even work according to Sun's own
283 documentation. (sa_flags is a 4 element array of
284 unsigned longs, with no mention of how one stores the
285 flags into it.) Let's just use plain old signal for
287 signal(SIGHUP, handle_sighup);
289 #ifdef SIGACTION_WORKING
290 sigact.sa_handler = handle_sighup;
293 sigact.sa_restorer = NULL;
294 sigaction(SIGHUP, &sigact, NULL);
296 signal(SIGHUP, handle_sighup);
300 /* No connection at outset */
303 /* Initialize storage system. */
304 InitStorage("%Y-%m/%Y-%m-%d-$b");
306 upsince = getzulutime(NULL);
308 fprintf(stderr, "Starting basket %s.\n", Version);
310 /* Inner loop is currently single thread:
311 1A. Wait for connection. When a connection comes in,
312 packet has been decrypted and verified.
313 1B. Validate connection.
314 1C. Reply to connection. */
316 /* If no current listening connection, try to get one,
317 and if that fails, sleep and try later. */
320 /* Get a listening socket at BASKETPORT.
322 sdlisten = InitNetwork(myaddr, myport);
329 /* 1A. Wait for connection. */
330 res = NetListen(sdlisten, &pktbuf, &rhost, TRUE);
332 fprintf(stderr, "NetListen error: %d\n", res);
336 /* 1B. Validate connection.
337 Remote host address is in rhost. Make sure this is either an
338 egg or a basket, and set the flag appropriately. */
340 ipvalidate = ntohl(rhost.sin_addr.s_addr);
343 isegg = isbasket = 0;
344 for (i = 0; i < numeggs; i++) {
346 /* When mask == 32: ipmask = 0xFFFFFFFF (all significant)
347 mask == 16: ipmask = 0xFFFF0000 (only top half sig)
348 mask == 0: ipmask = 0x00000000 (none significant) */
349 ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
350 ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
351 if ((ipvalidate & ipmask) == (ipmatch & ipmask)) {
352 isegg = eggtable[i].id;
354 remname = eggtable[i].name;
357 if (!memcmp(&(rhost.sin_addr),
358 &(eggtable[i].ipaddr.sin_addr),
359 sizeof(rhost.sin_addr))) {
360 isegg = eggtable[i].id;
362 remname = eggtable[i].name;
366 for (i = 0; i < numbaskets; i++) {
367 if (!memcmp(&(rhost.sin_addr),
368 &(baskettable[i].ipaddr.sin_addr),
369 sizeof(rhost.sin_addr))) {
371 remname = baskettable[i].name;
375 if (!isegg && !isbasket) {
376 fprintf(stderr, "Attempt to connect from unknown source: %s\n",
377 sockaddr2dquad(&rhost));
381 memcpy(&pkttype, pktbuf, sizeof(uint16));
382 memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
383 pkttype = ntohs(pkttype);
384 pktsize = ntohs(pktsize);
386 fprintf(stderr, "Packet doesn't contain an egg id, why?\n");
389 pkt_eggid = ntohs(*((uint16 *) (pktbuf + 4)));
391 /* Am I hearing from who I think I am? */
393 /* Have to assume the best case before testing: that the
394 pkt_eggid is actually correct. */
395 for (i = 0; i < numeggs; i++) if (eggtable[i].id == pkt_eggid) break;
398 /* Must be a spoof, and possibly an attempt to crash software too. */
399 fprintf(stderr, "%s: Egg spoofing? %s reporting itself as %d\n",
400 pgmname, hl2dquad(ipvalidate), pkt_eggid);
404 ipmask = (0xFFFFFFFF << (32-eggtable[i].netmask));
405 ipmatch = ntohl(eggtable[i].ipaddr.sin_addr.s_addr);
406 if ((ipvalidate & ipmask) != (ipmatch & ipmask)) {
407 fprintf(stderr, "%s: Egg spoofing? %s reporting itself as %d\n",
408 pgmname, hl2dquad(ipvalidate), pkt_eggid);
411 isegg = eggtable[i].id;
413 remname = eggtable[i].name;
416 if (isegg != pkt_eggid) {
417 fprintf(stderr, "%s: Egg spoofing? %d reporting itself as %d\n",
418 pgmname, isegg, pkt_eggid);
423 /* 1D. Reply to connection. */
427 BasketReceiveDataPacket(pktbuf, isegg, thisegg, &rhost);
431 /* Is this how baskets communicate? */
438 memcpy(&egg_now_time, pktbuf + 6, sizeof egg_now_time);
439 egg_now_time = ntohl(egg_now_time);
440 fprintf(stderr, "Awake egg %d (%s) at its %lu: %s",
441 pkt_eggid, eggtable[thisegg].name, egg_now_time,
442 asctime(gmtime((time_t *) &egg_now_time)));
446 /* Calculate last data received from this egg, decide what
448 MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
449 rhost.sin_port = htons(EGGPORT);
450 res = NetTalk(&rhost, (char *)&rpkt, TRUE);
451 if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
453 /* If setup looks like it's out of date, correct it. */
454 if (!eggtable[thisegg].setup) {
455 MakeSettings(&spkt, thisegg);
456 rhost.sin_port = htons(EGGPORT);
457 res = NetTalk(&rhost, (char *)&spkt, TRUE);
458 if (res < 0) fprintf(stderr, "NetTalk error %d.\n", res);
462 case SETTINGS_PACKET:
463 /* I don't have settings, Bozo. */
470 /* handle_sighup -- Catch SIGHUP and reload configuraton files. */
472 static void handle_sighup(int arg) {
473 /* Reread the RC file */
477 fprintf(stderr, "Received SIGHUP: reloading configuration files.\n");
479 #ifndef SIGACTION_WORKING
480 signal(SIGHUP, handle_sighup); /* Reset signal handler for bottom-feeder signal() */
484 /* LoadRCFile -- Load basket configuration file. */
486 static void LoadRCFile(void) {
489 char *myargv[MAX_PARSE], *tp;
490 int myargc, lcount, p, i;
492 if ((fp = fopen(".basketrc", "r")) == NULL) {
493 if ((fp = fopen("~/.basketrc", "r")) == NULL) {
494 if ((fp = fopen("/etc/basketrc", "r")) == NULL) {
495 fprintf(stderr, "%s: Couldn't find a basket RC file.\n", pgmname);
501 numeggs = numbaskets = 0;
504 while(fgets(linebuf, 200, fp) != NULL) {
505 /* Comments and blank lines ignored. */
507 if (*linebuf == '#' || *linebuf == '\n') continue;
508 Parse(linebuf, &myargc, myargv);
509 if (myargc == 0) continue;
510 if (!strcmp(myargv[0], "EGG")) {
511 if (myargc < 7 || myargc > 8) {
512 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
513 pgmname, lcount, myargv[0]);
517 eggtable[numeggs].name = mallocpy(myargv[1]);
518 eggtable[numeggs].id = atoi(myargv[2]);
519 dquad2sockaddr(&(eggtable[numeggs].ipaddr),
520 &(eggtable[numeggs].netmask),
522 eggtable[numeggs].primbasket = mallocpy(myargv[4]);
523 eggtable[numeggs].conntype = (!strcmp(myargv[5], "PERM"))?CONN_PERM:CONN_DND;
524 eggtable[numeggs].connival = atoi(myargv[6]);
525 eggtable[numeggs].url = NULL;
526 if ((myargc > 7) && (strcmp(myargv[7], ".") != 0)) {
527 eggtable[numeggs].url = mallocpy(myargv[7]);
530 } else if (!strcmp(myargv[0], "BASKET")) {
532 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
533 pgmname, lcount, myargv[0]);
536 baskettable[numbaskets].name = mallocpy(myargv[1]);
537 dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
539 } else if (!strcmp(myargv[0], "PROTOCOL")) {
541 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
542 pgmname, lcount, myargv[0]);
545 protocol.samp_rec = atoi(myargv[1]);
546 protocol.sec_rec = atoi(myargv[2]);
547 protocol.rec_pkt = atoi(myargv[3]);
548 protocol.trialsz = atoi(myargv[4]);
549 } else if (!strcmp(myargv[0], "HTML")) {
551 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
552 pgmname, lcount, myargv[0]);
555 htmlInterval = atoi(myargv[1]);
556 strcpy(htmlFile, myargv[2]);
558 fprintf(stderr, "Updating HTML file %s every %d seconds.\n", htmlFile, htmlInterval);
560 } else if (!strcmp(myargv[0], "PORT") ||
561 !strcmp(myargv[0], "INTERFACE")) {
563 fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
564 pgmname, lcount, myargv[0]);
567 tp = (char *) malloc(200);
568 for (*tp = 0, p = 1; p < myargc; p++) {
569 strcat(tp, myargv[p]);
570 if (p < myargc-1) strcat(tp, " ");
573 if (!strcmp(myargv[0], "PORT")) {
575 fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
576 pgmname, atoi(tp), myport);
581 } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
583 fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[0]);
590 /* Report a list of the eggs that we are accepting. */
591 for (i = 0; i < numeggs; i++) {
592 fprintf(stderr, "Egg %2d:\tid = %4d\tip=%s/%d\n",
594 sockaddr2dquad(&(eggtable[i].ipaddr)),
595 eggtable[i].netmask);
600 /* BasketReceiveDataPacket -- Process a data packet received frm an egg. */
602 static int32 BasketReceiveDataPacket(char *pkt, int16 isegg, int16 thisegg,
603 struct sockaddr_in *rhost) {
617 /* Unpack the portable header into a host-order and aligned
621 unpackShort(dpk.type);
622 unpackShort(dpk.pktsize);
623 unpackShort(dpk.eggid);
624 unpackShort(dpk.samp_rec);
625 unpackShort(dpk.sec_rec);
626 unpackShort(dpk.rec_pkt);
627 unpackByte(dpk.trialsz);
628 unpackShort(dpk.numrec);
630 /* This spoofing test is probably completely redundant now. */
632 /* Am I hearing from an egg, and is it who I think it is? */
633 if (isegg && isegg != dpktp->eggid) {
634 fprintf(stderr, "%s: Egg spoofing? %d reporting itself as %d\n",
635 pgmname, isegg, dpktp->eggid);
640 /* Save the packet header in the result EggCarton buffer. */
642 memcpy(&(result.hdr), dpktp, sizeof(EggHeader));
643 offset = sizeof(EggHeader);
650 if (result.hdr.numrec > 0) {
654 fprintf(stderr, "Received packet: %d records from egg %d (%s).\n",
655 result.hdr.numrec, dpktp->eggid, eggtable[thisegg].name);
659 /* Unpack the individual data records from the packet and
660 transcribe to the result EggCarton. */
662 for (latest = 0, rec = 0; rec < result.hdr.numrec; rec++) {
663 unpackLong(result.records[rec].timestamp); /* Record timestamp */
664 if (result.records[rec].timestamp > latest)
665 latest = result.records[rec].timestamp;
667 fprintf(stderr, " %10lu ", result.records[rec].timestamp);
669 /* Assumes sizeof(trial) = 1 */
670 unpackBytes(&(result.records[rec].trials), result.hdr.samp_rec);
672 for (i = 0; i < result.hdr.samp_rec; i++) {
673 fprintf(stderr, "%3d ", result.records[rec].trials[i]);
675 fprintf(stderr, "%s", asctime(gmtime((time_t *) &result.records[rec].timestamp)));
679 /* Save the packet in the basket database file. */
681 if ((res = SavePacket(&result)) < 0) return res;
683 /* If we're generating an HTML report, update the per-basket
684 statistics based on the content of this packet. */
688 uint32 ptrials = 0, psum = 0, dropped = 0;
690 /* Update the egg status for HTML report. */
692 for (r = 0; r < result.hdr.rec_pkt; r++) {
693 for (t = 0; t < result.hdr.samp_rec; t++) {
694 if (result.records[r].trials[t] != EGG_MISSING_DATA) {
696 psum += result.records[r].trials[t];
702 eggStatistics[thisegg].trials += ptrials;
703 eggStatistics[thisegg].sum += psum;
705 /* If this is the first packet received from the egg, and
706 the first sample in it is missing, don't count missing
707 samples against this egg, since it's probable this is
708 the first packet collected since the egg started, which
709 will contain initial missing samples for time before the
712 if (eggStatistics[thisegg].firstPacket != 0) {
713 eggStatistics[thisegg].missing += dropped;
715 eggStatistics[thisegg].firstPacket = result.records[0].timestamp;
719 if (latest > eggtable[thisegg].lastupd) {
720 eggtable[thisegg].lastupd = latest;
722 updateHTML(); /* Update HTML status file, if warranted */
723 if (latest < getzulutime(NULL)) {
724 /* Get more data if it looks like there should be more. */
725 MakeRequest(&rpkt, isegg, eggtable[thisegg].lastupd);
726 rhost->sin_port = htons(EGGPORT);
727 res = NetTalk(rhost, (char *)&rpkt, TRUE);
728 if (res < 0) fprintf(stderr, "NetTalk error %d.\n", (int)res);
732 /* If the protocol now in effect differs from that of the
733 packet just received from the egg, mark the egg as not
734 set-up, and thus in need of a SETTINGS_PACKET to reset
737 eggtable[thisegg].setup = (protocol.samp_rec == dpktp->samp_rec &&
738 protocol.sec_rec == dpktp->sec_rec &&
739 protocol.rec_pkt == dpktp->rec_pkt &&
740 protocol.trialsz == dpktp->trialsz);
745 /* MakeRequest -- Assemble a request packet for a given egg,
746 specifying, in whence, the time and date
747 of the last sample received from that egg. */
749 static void MakeRequest(ReqPacket *pkt, uint16 eggid, uint32 whence) {
750 char *pktP = (char *) pkt;
752 packShort(REQ_PACKET);
753 packShort((4 * sizeof(uint16)) + sizeof(uint32));
758 /* MakeSettings -- Create a settings packet. This packet,
759 sent periodically to connected eggs,
760 informs them of any change in protocol. */
762 static void MakeSettings(SettingsPacket *pkt, uint16 eggid) {
763 char *pktP = (char *) pkt;
765 packShort(SETTINGS_PACKET);
766 packShort((7 * sizeof(uint16)) + sizeof(uint32) + sizeof(trial));
768 packLong(getzulutime(NULL));
769 packShort(protocol.samp_rec);
770 packShort(protocol.sec_rec);
771 packShort(protocol.rec_pkt);
772 packByte(protocol.trialsz);
775 /* LoadEggStats -- Initialise in-memory egg status table
776 from the EGGSTATS file. */
778 static void LoadEggStats(void) {
780 char linebuf[200], *myargv[MAX_PARSE], *name;
781 int i, f, myargc, id, setup;
785 for (i = 0; i < numeggs; i++) {
786 eggtable[i].lastupd = 0;
787 eggtable[i].setup = 0;
790 if ((fp = fopen(EGGSTATS, "r")) == NULL) return;
792 while(fgets(linebuf, 200, fp) != NULL) {
793 /* Comments and blank lines ignored. */
794 if (*linebuf == '#' || *linebuf == '\n') continue;
795 Parse(linebuf, &myargc, myargv);
796 if (myargc == 0) continue;
797 if (!strcmp(myargv[0], "STAT") && myargc == 5) {
799 id = atoi(myargv[2]);
800 lastupd = atol(myargv[3]);
801 setup = atoi(myargv[4]);
802 for (f = 0, i = 0; i < numeggs; i++) {
803 if (eggtable[i].id == id &&
804 !strcmp(eggtable[i].name, name)) {
805 eggtable[i].lastupd = lastupd;
806 eggtable[i].setup = setup;
810 if (!f) fprintf(stderr, "%s: Egg '%s' no longer hosted.\n", pgmname, name);
816 /* SaveEggStats -- Dump in-memory egg status to the EGGSTATS file. */
818 static void SaveEggStats(void) {
822 if ((fp = fopen(EGGSTATS, "w")) == NULL) {
823 /* Couldn't write stats! */
824 fprintf(stderr, "%s: Couldn't save egg stats!\n", pgmname);
828 fprintf(fp, "# Status lines of form:\n");
829 fprintf(fp, "# STAT <eggname> <eggid> <lastupdzulu> <setupvalid>\n");
830 for (i = 0; i < numeggs; i++) {
831 fprintf(fp, "STAT %s %d %ld %d\n",