+/* PROGRAM: eggsh
+ * FILE: $Header: /home/egg/src/RCS/egg.c,v 1.8 1999/02/28 20:01:43 ghn Exp $
+ * PURPOSE: EGG site data collection
+ * AUTHOR: Greg Nelson
+ * DATE: 98-06-20
+ *
+ * REVISED:
+ * $Log: egg.c,v $
+ * Revision 1.8 1999/02/28 20:01:43 ghn
+ * Version 5.1: Added command line parsing for interface/port configuration.
+ * Changed default interface to 0.0.0.0, so that egg will normally bind to
+ * all incoming interfaces. Reports interface/port it is using, right before
+ * the screen gets cleared and you can't see it.
+ *
+ * Revision 1.7 1999/01/01 23:57:20 ghn
+ * Remove excess code associated with CPU-bound version and "OLDWAY" UI
+ * and egg HW selection. Add back in updated block algorithm description.
+ * Allow non-CPU-bound version to exchange more than 1 packet per second.
+ * Get rid of warnings when network is not reachable, assuming this is a
+ * transient error.
+ *
+ * Revision 1.6 1998/12/31 22:07:56 ghn
+ * Rev 5 code: includes multi-reg support, HTML, etc.
+ *
+ * Revision 1.5 1998/08/03 20:35:55 kelvin
+ * PACKETDUMP support.
+ *
+ * Revision 1.4 1998/08/01 21:34:26 ghn
+ * Connected user interface and added PSEUDO suppor into main line.
+ *
+ * Revision 1.3 1998/08/01 18:51:25 ghn
+ * Added John's byte-order-independence changes.
+ *
+ * Revision 1.2 1998/08/01 17:04:26 ghn
+ * Lots of fixes from John, plus DND support.
+ *
+ * Revision 1.1 1998/07/21 11:41:35 ghn
+ * Initial revision
+ *
+ * Copyright 1998 - Greg Nelson
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include "global.h"
+#include "genlib.h"
+#include "storage.h"
+#include "network.h"
+#include "eggui.h"
+#include "errnos.h"
+#include "regs.h"
+#include "regtable.h"
+#include "version.h"
+
+/* Good old Linux doesn't define the following functions
+ by default in the #include files the manual page claims
+ it does, and the conditional declarations with __USE_xx
+ doesn't work. Explicitly declare them, which doesn't
+ seem to do any harm on systems where these functions are
+ properly declared. If you get errors on the following
+ declarations, you can probably get around them by #ifdef-ing
+ the declarations off for your platform. */
+
+extern int nice(int inc);
+extern int strcasecmp(const char *s1, const char *s2);
+
+/* Prototypes for forward functions. */
+
+static void MakeAwake(AwakePacket *pkt);
+static void MakeDataPkt(char **pkt, EggCarton *src);
+static void LoadRCFile(char *filename);
+static double SetCollOpts(void);
+static int GreetBasket(void);
+static void handle_sigkill(int arg), handle_sighup(int arg);
+
+EggEntry eggtable[MAX_EGGS];
+BasketEntry baskettable[MAX_BASKETS];
+EggHeader protocol;
+REG_driver *configuredREG = NULL; /* Configured REG driver */
+
+short numeggs = 0, numbaskets = 0;
+
+static DevOpts devopts; /* Device options for REG */
+static CollectRecord coll;
+static EggCarton savebuffer;
+char *pgmname; /* Program name from argv[0] */
+char *myaddr; /* Interface to bind */
+int16 myport; /* Service port to bind */
+int32 lastDataSent = 0; /* Time last packet sent to basket */
+
+/* If no priority increment has been specified at compile time, set to
+ our default of ±10. [+/- 10, for folks without 8bit editors] */
+
+#ifndef NICE
+#define NICE 10
+#endif
+static int niceness = NICE; /* Priority increment/decrement for collection */
+
+/* Status exported to user interface. */
+
+int32 time_latency = 0, time_housekeeping = 0;
+
+#ifdef ALT_UI
+static double mean_Packet = 0.0, mean_Grand = 0.0;
+static int32 total_Packets = 0;
+#endif
+
+/* resetCarton -- Clear existing data from savebuffer and
+ reinitialise for a sampling interval
+ beginning at the specified start_time. */
+
+static void resetCarton(uint32 start_time)
+{
+ int i;
+ uint32 sec_pkt;
+
+ /* Save current collection options to packet. */
+
+ memcpy(&(savebuffer.hdr), &(coll.opts), sizeof(EggHeader));
+
+ /* Set number of records in packet and clear record
+ buffers to missing data. */
+
+ savebuffer.hdr.numrec = savebuffer.hdr.rec_pkt; /* Mark all records present */
+
+ /* Align start_time to even multiple of seconds per
+ packet. */
+
+ sec_pkt = savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt;
+ start_time = sec_pkt * (start_time / sec_pkt);
+
+ for (i = 0; i < savebuffer.hdr.rec_pkt; i++) {
+ savebuffer.records[i].timestamp = start_time;
+ start_time += savebuffer.hdr.sec_rec;
+#if MAXBITS < 256
+ memset(savebuffer.records[i].trials, EGG_MISSING_DATA, MAXSAMP_REC);
+#else
+#error "Can't represent MAXBITS in a byte value."
+#endif
+ }
+}
+
+/* saveProtocol -- Save current protocol in the .eggprotocolrc file. */
+
+static void saveProtocol(void)
+{
+ FILE *rcfile;
+
+ rcfile = fopen(".eggprotocolrc", "w");
+ if (rcfile == NULL) {
+ fprintf(stderr, "%s: Cannot create .eggprotocolrc file.\n", pgmname);
+ exit(-1);
+ }
+ fprintf(rcfile, "#\n# Current protocol for egg\n#\n");
+ fprintf(rcfile, "# PROTOCOL <samp_rec> <sec_rec> <rec_pkt> <trialsz>\n");
+ fprintf(rcfile, "PROTOCOL %d %d %d %d\n", protocol.samp_rec, protocol.sec_rec,
+ protocol.rec_pkt, protocol.trialsz);
+ fclose(rcfile);
+}
+
+static void Usage(void) {
+ printf("Usage: %s [-i interface] [-p port]\n", pgmname);
+ exit(-1);
+}
+
+/* Main program. */
+
+int main(int argc, char *argv[]) {
+ char *pktbuf, *outpktbuf;
+ uint16 pkttype, pktsize;
+ int sdlisten, res;
+ struct sockaddr_in rhost;
+ EggCarton retrcart;
+ int i, isbasket;
+ char *remname;
+ int32 lastconn, lastsend;
+ double sps;
+#ifdef SIGACTION_WORKING
+ struct sigaction sigact;
+#endif
+ FILE *pidfile, *rcfile;
+
+ pgmname = argv[0];
+ argv++; argc--;
+
+ /* Defaults correspond to original usage */
+ myport = EGGPORT;
+ myaddr = "0.0.0.0";
+
+ while (argc) {
+ if (argv[0][0] == '-') {
+ switch(argv[0][1]) {
+ case 'i': /* Specify interface */
+ if (argc < 2) Usage();
+ myaddr = argv[1];
+ argv++; argc--;
+ break;
+ case 'p': /* Specify port */
+ if (argc < 2) Usage();
+ myport = atoi(argv[1]);
+ if (myport < 0) Usage();
+ argv++; argc--;
+ break;
+ default: /* Oops. */
+ Usage();
+ }
+ } else {
+ Usage();
+ }
+ argv++; argc--;
+ }
+
+#ifdef Solaris
+ /* Solaris has an eccentric definition of sigaction() which
+ doesn't seem to even work according to Sun's own
+ documentation. (sa_flags is a 4 element array of
+ unsigned longs, with no mention of how one stores the
+ flags into it.) Let's just use plain old signal for
+ the moment. */
+ signal(SIGKILL, handle_sigkill);
+ signal(SIGHUP, handle_sighup);
+#else
+#ifdef SIGACTION_WORKING
+ sigact.sa_handler = handle_sigkill;
+ sigact.sa_mask = 0;
+ sigact.sa_flags = 0;
+ sigact.sa_restorer = NULL;
+ sigaction(SIGKILL, &sigact, NULL);
+
+ sigact.sa_handler = handle_sighup;
+ sigaction(SIGHUP, &sigact, NULL);
+#else
+ signal(SIGKILL, handle_sigkill);
+ signal(SIGHUP, handle_sighup);
+#endif
+#endif
+
+ fprintf(stderr, "Starting egg %s.\n", Version);
+
+ /* Save our process ID in a file for folks who might wish
+ to send us a signal. */
+
+ pidfile = fopen("eggsample.pid", "w");
+ if (pidfile == NULL) {
+ fprintf(stderr, "Unable to create eggsample.pid file.\n");
+ exit(-1);
+ }
+ fprintf(pidfile, "%d\n", getpid());
+ fclose(pidfile);
+
+ /* If we aren't being run by the super-user, disable the
+ priority raising and lowering mechanism. */
+
+ if (getuid() != 0) {
+ niceness = 0;
+ }
+
+#ifdef DEBUG
+ printf("User ID = %d. Niceness = %d.\n", getuid(), niceness);
+ printf("REG drivers installed: ");
+ for (i = 0; reg_table[i] != NULL; i++) {
+ printf(" %s", reg_table[i]->reg_name);
+ }
+ printf("\n");
+#endif
+
+ LoadRCFile(NULL);
+
+ /* If an auxiliary configuration file exists, load it on
+ top of the arguments loaded from the main RC file. If
+ it doesn't exist, create it. */
+
+ if ((rcfile = fopen(".eggprotocolrc", "r")) != NULL) {
+ fclose(rcfile);
+ LoadRCFile(".eggprotocolrc");
+ } else {
+ saveProtocol();
+ }
+
+ if (configuredREG == NULL) {
+ fprintf(stderr, "%s: RC error, no REG specified for egg.\n",
+ pgmname);
+ exit(-1);
+ }
+#ifdef DEBUG
+ else {
+ printf("REG configured: %s = %d, %d, %ld\n", configuredREG ->reg_name,
+ devopts.type, devopts.port, devopts.baud);
+ }
+#endif
+
+
+ sps = SetCollOpts();
+
+ if ((coll.dd = OpenDev(&devopts)) < 0) {
+ fprintf(stderr, "Couldn't talk to hardware device.\n");
+ exit(1);
+ }
+
+ if (EvalSpeed(coll.dd) < coll.opts.trialsz * sps) {
+ fprintf(stderr, "Requested speed exceeds device capabilities.\n");
+ exit(-1);
+ }
+
+#if REPORT > 0
+ if (myaddr) {
+ fprintf(stderr, "TCP configured: %s.%d\n", myaddr, myport);
+ } else {
+ fprintf(stderr, "TCP configured: hostname.%d\n", myport);
+ }
+#endif
+
+ UIInit();
+
+ sdlisten = -1; /* No conn yet. */
+ lastconn = lastsend = 0;
+
+ /* Initialise carton to zero time, indicating no data in
+ initial carton. */
+ resetCarton(0);
+
+ /* Initialize storage system */
+ InitStorage("%Y-%m/%Y-%m-%d-$06E");
+
+ /* Inner loop is currently two threads which are both processed
+ together through NBIO.
+
+ 2A. Adjust priority and sleep until time computed below (2H).
+ 2B. Busy wait until second ticks over.
+ 2C. Empty REG input buffer (if needed), then collect sample.
+ 2D. Lower priority to handle bookkeeping; save packet.
+ 2E. Update user interface.
+
+ 2F. Send awake packet if connival is passed.
+ 2G. [DND only] Put net to sleep if we've been waiting more than a minute
+
+ 1A. Wait for connection. When a connection comes in,
+ packet has been decrypted and verified.
+ 1B. Validate connection.
+ 1C. Reply to connection.
+ 1D. If we have more than MINSLEEP time left in second,
+ go back to 1A.
+
+ 2H. Compute new sleep time.
+ */
+
+ while (1) {
+ static int sleeptime = 0;
+ static struct timeval t, lt = {0, 0};
+ struct timeval ct;
+ trial sample;
+ int havedata;
+ uint32 rindex;
+#ifdef DEBUG
+ uint32 msec, usec;
+#endif
+
+#ifndef SLACK
+#define SLACK 50000 /* CPU loop resynchronisation time in microseconds */
+#endif
+
+#ifndef MINSLEEP
+#define MINSLEEP 100000 /* Minimum sleep time, usec */
+#endif
+
+ /* 2A. If we have successfully computed a sleep time until
+ the window opens to collect the next sample, give up
+ the CPU until that time arrives. */
+
+ if (niceness != 0) {
+ nice(-niceness); /* Raise priority for re-dispatch */
+ }
+ if (sleeptime > 0) {
+ Usleep((unsigned) sleeptime);
+ }
+
+ /* 2B. We're now close to the start of the next second. Watch the
+ time until the second changes, then collect the next sample.
+ Note that this method of doing things enforces the one
+ sample/second that has become standard practise. The code at
+ the bottom of the loop could be changed to allow for a longer
+ interval, but this code enforces a minimum of a 1-second change
+ for each sample. */
+
+ while (1) {
+ gettimeofday(&t, NULL);
+ if (lt.tv_sec == 0) lt.tv_sec = t.tv_sec;
+ if (t.tv_sec != lt.tv_sec) break;
+ }
+
+ /* 2C. It is now time to collect the next sample. Discard any
+ any data in the input queue and get the sample. */
+
+ Discard(coll.dd);
+ sample = Sample(coll.dd, coll.opts.trialsz);
+ if (niceness != 0) {
+ nice(niceness); /* Collection done. Lower priority to normal. */
+ }
+
+ /* 2D. Okay, we're now in "quality time"--the slack after collection
+ of a sample until the time for the next sample arrives.
+ Now is an excellent time to do all kinds of housekeeping.
+ First of all, see if the sample just collected fits into
+ the packet currently being assembled. If not, we need to
+ save the last packet in the egg data file and initialise
+ a new packet for the time period in which this sample was
+ collected. */
+
+ if ((savebuffer.records[0].timestamp == 0) ||
+ ((savebuffer.records[0].timestamp +
+ (savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt)) <= t.tv_sec)) {
+
+ if (savebuffer.records[0].timestamp != 0) {
+ static int firstpacket = 1;
+#ifdef ALT_UI
+ int r, t;
+ uint32 ptrials = 0, psum = 0;
+ static uint32 totalTrials = 0;
+ static double totalGrand = 0.0;
+#endif
+
+ /* Sample isn't within current packet. Dump packet to the egg
+ data file and reinitialise to the window containing the
+ sample we just collected. But first, a little gimmick. If
+ this is the first packet we've collected and it contains
+ one or more missing samples at the beginning, discard it.
+ This keeps the routine missing samples in the first packet
+ after the egg starts up from being reported as missing
+ samples due to genuine synchronisation errors. */
+
+ if (!(firstpacket && (savebuffer.records[0].trials[0] == EGG_MISSING_DATA))) {
+ SavePacket(&savebuffer);
+ }
+#ifdef DEBUG
+ else {
+ fprintf(stderr, "Dropping first packet to discard start-up missing samples.\n");
+ }
+#endif
+ firstpacket = 0;
+
+#ifdef ALT_UI
+ /* Update the last packet and grand mean data for the debug
+ interface status display. */
+
+ for (r = 0; r < savebuffer.hdr.rec_pkt; r++) {
+ for (t = 0; t < savebuffer.hdr.samp_rec; t++) {
+ if (savebuffer.records[r].trials[t] != EGG_MISSING_DATA) {
+ ptrials++;
+ psum += savebuffer.records[r].trials[t];
+ }
+ }
+ }
+ mean_Packet = ((double) psum) / ptrials;
+ totalGrand += psum;
+ totalTrials += ptrials;
+ mean_Grand = totalGrand / totalTrials;
+ total_Packets++;
+ printf("Packets sent: %ld Packet mean: %6.2f Grand mean: %6.2f\n",
+ total_Packets, mean_Packet, mean_Grand);
+#endif
+ }
+
+ /* Check for change to the collection parameters...
+ now is the time to implement it. */
+
+ if (coll.opts.rec_pkt != protocol.rec_pkt ||
+ coll.opts.trialsz != protocol.trialsz ||
+ coll.opts.sec_rec != protocol.sec_rec ||
+ coll.opts.samp_rec != protocol.samp_rec) {
+ sps = SetCollOpts();
+ saveProtocol();
+ }
+ /* Reinitialise carton to interval containing sample */
+ resetCarton(t.tv_sec);
+ }
+
+ /* Store the sample into the carton, which is now guaranteed to
+ bracket the interval in which it was collected. */
+
+#ifdef DEBUG
+ /* But let's be sure it really *is* in the interval. */
+ if (t.tv_sec < savebuffer.records[0].timestamp ||
+ t.tv_sec >= (savebuffer.records[0].timestamp + savebuffer.hdr.sec_rec * savebuffer.hdr.rec_pkt)) {
+ fprintf(stderr, "***Sample, collected at %ld, is not within packet starting at %ld.\n",
+ t.tv_sec, savebuffer.records[0].timestamp);
+ exit(-1);
+ }
+#endif
+
+ rindex = t.tv_sec - savebuffer.records[0].timestamp;
+ savebuffer.records[rindex / savebuffer.hdr.sec_rec].trials[rindex % savebuffer.hdr.sec_rec] = sample;
+
+ /* 2E. Update user interface. */
+
+#ifndef NO_UI
+ coll.data.trials[coll.sampct] = sample;
+ coll.sampct++;
+ UIUpdate(coll.sampct >= coll.opts.samp_rec, &coll);
+
+ if (coll.sampct >= coll.opts.samp_rec) {
+ coll.sampct = 0;
+ }
+#endif
+
+ /* Now that the current sample has been dealt which, check
+ for egg-initiated actions whose time has come. */
+
+ /* 2F. First of all, it it's time to prod the basket with an
+ awake packet, lob one over the pole. */
+
+ if (getzulutime(NULL) - lastconn > (eggtable[0].connival * 60L)) {
+ lastsend = lastconn = getzulutime(NULL);
+
+ /* If this is a dial-and-drop egg, connect to the network before
+ sending the packet. */
+
+ if (sdlisten < 0 && eggtable[0].conntype == CONN_DND) {
+ sdlisten = NetUp(eggtable[0].upcmd, myaddr, myport);
+ }
+
+ GreetBasket();
+ }
+
+ /* 2G. If this is a dial-and-drop egg and it's been more than a
+ minute since we sent anything, tear down the network connection. */
+
+ if (eggtable[0].conntype == CONN_DND && sdlisten >= 0) {
+ if ((getzulutime(NULL) - lastsend) > 60L) {
+ NetDown(eggtable[0].dncmd, sdlisten);
+ sdlisten = -1;
+ }
+ }
+
+ /* Now deal with basket-initiated requests received on the socket
+ since the last time around the loop. To improve throughput for
+ dialup connections, we may come back here several times if we
+ have more data and if time allows. */
+
+ do {
+ havedata = 0; /* Don't loop unless net activity happens. */
+
+ /* 1A. Wait for connection. */
+ if (eggtable[0].conntype == CONN_PERM) {
+ if (sdlisten < 0) {
+ /* Initialize networking, get a listening socket at EGGPORT */
+ sdlisten = InitNetwork(myaddr, myport);
+ }
+ }
+ if (sdlisten >= 0) {
+ res = NetListen(sdlisten, &pktbuf, &rhost, FALSE);
+ if (res < 0 && res != ERR_COMM_TMOUT) {
+ fprintf(stderr, "NetListen error: %d\n", res);
+ break; /* Out of do loop */
+ }
+ /* Data present, process networking thread */
+ if (res == ERR_NONE) {
+ /* 1B. Validate connection.
+ Remote host address is in rhost. Make sure this is either an
+ egg or a basket, and set the flag appropriately. */
+ isbasket = 0;
+ for (i = 0; i < numbaskets; i++) {
+ if (!memcmp(&(rhost.sin_addr),
+ &(baskettable[i].ipaddr.sin_addr),
+ sizeof(rhost.sin_addr))) {
+ isbasket = 1;
+ remname = baskettable[i].name;
+ }
+ }
+
+ if (!isbasket) {
+ fprintf(stderr, "Attempt to connect from unknown source: %s",
+ sockaddr2dquad(&rhost));
+ break; /* Out of do loop */
+ }
+
+ havedata = 1; /* Initial assumption, until disproven. */
+
+ memcpy(&pkttype, pktbuf, sizeof(uint16));
+ memcpy(&pktsize, pktbuf+sizeof(uint16), sizeof(uint16));
+ pkttype = ntohs(pkttype);
+ pktsize = ntohs(pktsize);
+
+ /* 1C. Reply to connection. */
+ switch(pkttype) {
+ case DATA_PACKET:
+ case AWAKE_PACKET:
+ /* Not acceptable at an eggsite. */
+ fprintf(stderr, "%s: EGG could not accept %d packet\n",
+ pgmname, pkttype);
+ break;
+
+ /* Request for data from a basket. */
+
+ case REQ_PACKET:
+ {
+ uint16 reggid;
+ uint32 stime;
+ char *pktP = pktbuf + (2 * sizeof(uint16));
+
+ unpackShort(reggid);
+ unpackLong(stime);
+#ifdef PACKETDUMP
+ fprintf(stderr, "Request: eggid = %d, starttm = %ld: %s",
+ reggid, stime, asctime(gmtime((time_t *) &stime)));
+#endif
+ res = LoadPacket(stime, reggid, &retrcart);
+ }
+ if (res == ERR_NONE) {
+ /* NetPacketize it */
+ MakeDataPkt(&outpktbuf, &retrcart);
+ rhost.sin_port = htons(BASKETPORT);
+ res = NetTalk(&rhost, outpktbuf, TRUE);
+ if (res < 0) {
+ fprintf(stderr, "NetTalk failed (%d)\n", res);
+ }
+ free(outpktbuf);
+ lastDataSent = lastsend = getzulutime(NULL);
+ } else if (res == ERR_EOF) {
+ /* End of data. If DND, bring down connection */
+ if (eggtable[0].conntype == CONN_DND) {
+ NetDown(eggtable[0].dncmd, sdlisten);
+ sdlisten = -1;
+ }
+ havedata = 0;
+ }
+ break;
+
+ case SETTINGS_PACKET:
+ /* Update settings in buffer, will change at next packet
+ boundary. */
+
+ { char *pktP = pktbuf + (3 * sizeof(uint16)) + sizeof(uint32);
+
+ unpackShort(protocol.samp_rec);
+ unpackShort(protocol.sec_rec);
+ unpackShort(protocol.rec_pkt);
+ unpackByte(protocol.trialsz);
+ }
+#ifdef PACKETDUMP
+ fprintf(stderr, "Settings: samp_rec = %d, sec_rec = %d, rec_pkt = %d, trialsz = %d\n",
+ protocol.samp_rec, protocol.sec_rec, protocol.rec_pkt, protocol.trialsz);
+#endif
+ break;
+ }
+ free(pktbuf);
+ }
+ }
+ gettimeofday(&ct, NULL);
+ } while (havedata && ct.tv_usec < 1000000L - MINSLEEP);
+
+ /* 2H. Compute the length in microseconds we should sleep before
+ the top of the next second. Note that we compute the interval
+ based on the time at the bottom of the loop, after dealing with
+ whatever housekeeping requests may have been performed since
+ collecting the last sample. */
+
+ gettimeofday(&ct, NULL);
+ if (lt.tv_sec == 0) {
+ lt.tv_sec = t.tv_sec - 1;
+ }
+ time_latency = (((t.tv_sec - lt.tv_sec) - 1) * 1000000L) + t.tv_usec;
+ time_housekeeping = ((ct.tv_sec - t.tv_sec) * 1000000L) + (ct.tv_usec - t.tv_usec);
+#ifdef DEBUG
+ usec = time_latency;
+ msec = usec / 1000;
+ usec %= 1000;
+ printf("Sampling latency: %ld.%03ld msec", msec, usec);
+
+ usec = time_housekeeping;
+ msec = usec / 1000;
+ usec %= 1000;
+ printf(" Housekeeping time: %ld.%03ld msec\n", msec, usec);
+#endif
+ lt = ct;
+ sleeptime = ((1000000 - SLACK) - ct.tv_usec);
+ /* printf("Sleep time = %d usec\n", sleeptime); */
+ }
+}
+
+/* EGG specific */
+
+/* LoadRCFile -- Read in a configuration file. If the filename
+ argument is NULL, the default file is read.
+ Otherwise, configuration statements are read
+ from the file given by the argument. */
+
+static void LoadRCFile(char *filename) {
+ FILE *fp;
+ char linebuf[200];
+ char *myargv[MAX_PARSE], *tp;
+ int myargc, lcount, p, b, i;
+
+ if (filename != NULL) {
+ if ((fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open auxiliary configuration file %s.\n", pgmname, filename);
+ exit(-1);
+ }
+ } else {
+ if ((fp = fopen(".eggrc", "r")) == NULL) {
+ if ((fp = fopen("~/.eggrc", "r")) == NULL) {
+ if ((fp = fopen("/etc/eggrc", "r")) == NULL) {
+ fprintf(stderr, "%s: Couldn't find a egg RC file.\n", pgmname);
+ exit(-1);
+ }
+ }
+ }
+ }
+
+ lcount = 0;
+
+ while(fgets(linebuf, 200, fp) != NULL) {
+ /* Comments and blank lines ignored. */
+ lcount++;
+ if (*linebuf == '#' || *linebuf == '\n') continue;
+ Parse(linebuf, &myargc, myargv);
+ if (myargc == 0) continue;
+ if (!strcmp(myargv[0], "EGG")) {
+ if (myargc < 7 || myargc > 8) {
+ fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
+ pgmname, lcount, myargv[0]);
+ exit(-1);
+ }
+ if (numeggs > 0) {
+ fprintf(stderr, "%s: RC error, line %d, only one EGG allowed\n",
+ pgmname, lcount);
+ exit(-1);
+ }
+
+ eggtable[0].name = mallocpy(myargv[1]);
+ eggtable[0].id = atoi(myargv[2]);
+ dquad2sockaddr(&(eggtable[0].ipaddr), NULL, myargv[3]);
+ eggtable[0].primbasket = mallocpy(myargv[4]);
+ eggtable[0].conntype = (!strcmp(myargv[5], "PERM"))?CONN_PERM:CONN_DND;
+ eggtable[0].connival = atoi(myargv[6]);
+ eggtable[0].url = NULL; /* URL specification is permitted, but ignored */
+ numeggs = 1;
+ } else if (!strcmp(myargv[0], "BASKET")) {
+ if (myargc != 3) {
+ fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
+ pgmname, lcount, myargv[0]);
+ exit(-1);
+ }
+ baskettable[numbaskets].name = mallocpy(myargv[1]);
+ dquad2sockaddr(&(baskettable[numbaskets].ipaddr), NULL, myargv[2]);
+ numbaskets++;
+ } else if (!strcmp(myargv[0], "PROTOCOL")) {
+ if (myargc != 5) {
+ fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
+ pgmname, lcount, myargv[0]);
+ exit(-1);
+ }
+ protocol.samp_rec = atoi(myargv[1]);
+ protocol.sec_rec = atoi(myargv[2]);
+ protocol.rec_pkt = atoi(myargv[3]);
+ protocol.trialsz = atoi(myargv[4]);
+
+ /* REG: Configure Random Event Generator for this egg. */
+
+ } else if (!strcmp(myargv[0], "REG")) {
+ if (myargc != 4) {
+ fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
+ pgmname, lcount, myargv[0]);
+ exit(-1);
+ }
+ if (configuredREG != NULL) {
+ fprintf(stderr, "%s: RC error, line %d, multiple REGs specified.\n",
+ pgmname, lcount);
+ exit(-1);
+ }
+ for (i = 0; reg_table[i] != NULL; i++) {
+ if (strcasecmp(myargv[1], reg_table[i]->reg_name) == 0) {
+ configuredREG = reg_table[i];
+ devopts.type = reg_table[i]->reg_type;
+ }
+ }
+ if (configuredREG == NULL) {
+ fprintf(stderr, "%s: RC error, line %d, unknown REG type %s specified.\n",
+ pgmname, lcount, myargv[1]);
+ exit(-1);
+ }
+
+ devopts.port = atoi(myargv[2]);
+ devopts.baud = atoi(myargv[3]);
+ } else if (!strcmp(myargv[0], "NETUP") ||
+ !strcmp(myargv[0], "NETDOWN") ||
+ !strcmp(myargv[0], "PORT") ||
+ !strcmp(myargv[0], "INTERFACE")) {
+ if (myargc < 2) {
+ fprintf(stderr, "%s: RC error, line %d, poor %s format\n",
+ pgmname, lcount, myargv[0]);
+ exit(-1);
+ }
+ tp = (char *) malloc(200);
+ for (*tp = 0, p = 1; p < myargc; p++) {
+ strcat(tp, myargv[p]);
+ if (p < myargc-1) strcat(tp, " ");
+ }
+
+ if (!strcmp(myargv[0], "NETUP")) eggtable[0].upcmd = tp;
+ else if (!strcmp(myargv[0], "NETDOWN")) eggtable[0].dncmd = tp;
+ else if (!strcmp(myargv[0], "PORT")) {
+ if (atoi(tp) <= 0) {
+ fprintf(stderr, "%s: RC error, ignoring bad port %d, using %d\n",
+ pgmname, atoi(tp), myport);
+ } else {
+ myport = atoi(tp);
+ }
+ free(tp);
+ } else if (!strcmp(myargv[0], "INTERFACE")) myaddr = tp;
+ } else {
+ fprintf(stderr, "%s: RC error, %s is unknown keyword\n", pgmname, myargv[0]);
+ exit(-1);
+ }
+ }
+
+ /* Consistency checks done only after loading the main
+ configuration file. */
+
+ if (filename == NULL) {
+ for (p = -1, b = 0; b < numbaskets; b++) {
+ if (!strcmp(baskettable[b].name, eggtable[0].primbasket)) p = b;
+ }
+
+ if (p != 0) {
+ fprintf(stderr, "%s: RC error, primary basket should be listed first.\n", pgmname);
+ exit(-1);
+ }
+ } else {
+ printf("Reloaded auxiliary configuration file %s.\n", filename);
+ }
+ fclose(fp);
+}
+
+/* MakeAwake -- Make a egg awake packet to let the basket
+ know we're on line. */
+
+static void MakeAwake(AwakePacket *pkt) {
+ char *pktP = (char *) pkt;
+
+ packShort(AWAKE_PACKET);
+ packShort((4 * sizeof(uint16)) + sizeof(uint32));
+ packShort(eggtable[0].id);
+ packLong(getzulutime(NULL));
+}
+
+/* MakeDataPkt -- Build a canonical network byte order data packet
+ from the data collected in an EggCarton. */
+
+static void MakeDataPkt(char **pkt, EggCarton *src) {
+ uint16 pktsize, rec;
+ char *pktP;
+
+ pktsize = (7 * sizeof(uint16)) + sizeof(trial) + /* Header */
+ (src->hdr.numrec * (sizeof(uint32) + /* Trial data */
+ src->hdr.samp_rec * sizeof(trial))) +
+ sizeof(uint16); /* CRC-16 */
+ *pkt = pktP = (char *) malloc(pktsize);
+
+ /* Assemble header fields into data packet. */
+
+ packShort(src->hdr.type = DATA_PACKET);
+ packShort(src->hdr.pktsize = pktsize);
+ packShort(src->hdr.eggid);
+ packShort(src->hdr.samp_rec);
+ packShort(src->hdr.sec_rec);
+ packShort(src->hdr.rec_pkt);
+ packByte(src->hdr.trialsz);
+ packShort(src->hdr.numrec);
+
+ /* Append data records to packet. */
+
+ for (rec = 0; rec < src->hdr.numrec; rec++) {
+ packLong(src->records[rec].timestamp);
+ packBytes(&(src->records[rec].trials), src->hdr.samp_rec);
+ }
+
+ /* No need to calculate CRC -- NetTalk does that for us.
+ Note that we did reserve space for the CRC when
+ allocating the packet buffer. */
+}
+
+/* SetCollOpts -- Set collection options from the protocol
+ specified in the .rc file or by a basket. */
+
+static double SetCollOpts(void) {
+ double sps;
+
+ coll.opts.eggid = eggtable[0].id;
+
+ coll.opts.rec_pkt = protocol.rec_pkt;
+ if (coll.opts.rec_pkt < 1) coll.opts.rec_pkt = 1;
+ if (coll.opts.rec_pkt > MAXREC_PKT) coll.opts.rec_pkt = MAXREC_PKT;
+
+ coll.opts.trialsz = protocol.trialsz;
+ if (coll.opts.trialsz < MINBITS) {
+ fprintf(stderr, "Attempt to set trial size below %d, to %d.\n",
+ MINBITS, coll.opts.trialsz);
+ coll.opts.trialsz = MINBITS;
+ }
+ if (coll.opts.trialsz > MAXBITS) {
+ fprintf(stderr, "Attempt to set trial size above %d, to %d.\n",
+ MAXBITS, coll.opts.trialsz);
+ coll.opts.trialsz = MAXBITS;
+ }
+
+ coll.opts.sec_rec = protocol.sec_rec;
+ if (coll.opts.sec_rec < 1) {
+ fprintf(stderr, "Attempt to set seconds/record below 1, to %d.\n",
+ coll.opts.sec_rec);
+ coll.opts.sec_rec = 1;
+ }
+ if (coll.opts.sec_rec > MAXSEC_REC) {
+ fprintf(stderr, "Attempt to set seconds/record above %d, to %d.\n",
+ MAXSEC_REC, coll.opts.sec_rec);
+ coll.opts.sec_rec = MAXSEC_REC;
+ }
+
+ coll.opts.samp_rec = protocol.samp_rec;
+ if (coll.opts.samp_rec < 1) {
+ fprintf(stderr, "Attempt to set samples/record below 1, to %d.\n",
+ coll.opts.samp_rec);
+ coll.opts.samp_rec = 1;
+ }
+ if (coll.opts.samp_rec > MAXSAMP_REC) {
+ fprintf(stderr, "Attempt to set samples/record above %d, to %d.\n",
+ MAXSAMP_REC, coll.opts.samp_rec);
+ coll.opts.samp_rec = MAXSAMP_REC;
+ }
+
+ sps = (double)coll.opts.samp_rec / (double)coll.opts.sec_rec;
+
+ fprintf(stderr, "Effective sample rate is about %f samp/sec or %f bits/sec\n",
+ sps, coll.opts.trialsz * sps);
+ fprintf(stderr, "Packets contain %d records\n", coll.opts.rec_pkt);
+
+ return sps;
+}
+
+/* GreetBasket -- Send an awake packet to each configured
+ basket. */
+
+static int GreetBasket(void) {
+ int i, b;
+ struct sockaddr_in bhost;
+ AwakePacket awake;
+
+ MakeAwake(&awake);
+
+ for (b = 0; b < numbaskets; b++) {
+ memset(&bhost, 0, sizeof(struct sockaddr_in));
+ memcpy(&bhost, &(baskettable[b].ipaddr), sizeof(bhost));
+ bhost.sin_port = htons(BASKETPORT);
+ bhost.sin_family = AF_INET;
+
+ /* Don't gripe about network unreachable. Just means
+ network is down right now, try later. */
+ i = NetTalk(&bhost, (char *)&awake, FALSE);
+ if (i < 0) {
+ /* Couldn't get to this one, try others. */
+#ifdef DEBUG
+ fprintf(stderr, "%s: Failure to reach basket '%s'\n",
+ pgmname, baskettable[b].name);
+#endif
+ } else {
+ return ERR_NONE;
+ }
+ }
+
+ return ERR_COMM_TMOUT;
+}
+
+/* handle_sigkill -- KILL signal handler. Terminate user interface
+ and exit. */
+
+static void handle_sigkill(int arg) {
+ UIClose();
+ exit(-1);
+}
+
+/* handle_sighup -- HUP signal handler. Reload protocol from the
+ .eggprotocolrc file. */
+
+static void handle_sighup(int arg) {
+ LoadRCFile(".eggprotocolrc");
+#ifndef SIGACTION_WORKING
+ signal(SIGHUP, handle_sighup);
+#endif
+}