summaryrefslogtreecommitdiffstats
path: root/kstars/kstars/indi/indiserver.c
diff options
context:
space:
mode:
Diffstat (limited to 'kstars/kstars/indi/indiserver.c')
-rw-r--r--kstars/kstars/indi/indiserver.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/kstars/kstars/indi/indiserver.c b/kstars/kstars/indi/indiserver.c
new file mode 100644
index 00000000..ad4d2ffd
--- /dev/null
+++ b/kstars/kstars/indi/indiserver.c
@@ -0,0 +1,878 @@
+/* INDI Server.
+ * Copyright (C) 2005 Elwood C. Downey ecdowney@clearskyinstitute.com
+ * licensed under GNU Lesser Public License version 2.1 or later.
+ *
+ * argv lists names of Driver programs to run, they are restarted if they exit.
+ * Each Driver's stdin/out are assumed to provide INDI traffic and are connected
+ * here via pipes. Drivers' stderr are connected to our stderr.
+ * Clients can come and go as they please and will see messages only for Devices
+ * for which they have queried via getProperties.
+ * all newXXX() received from one Client are sent to all other Clients who have
+ * shown an interest in the same Device.
+ *
+ * Implementation notes:
+ *
+ * Each Client is written to by its own thread to allow for wildly different
+ * consumption rates. The main thread cracks the xml. When it sees a complete
+ * message it puts it in a new Msg which is the XMLEle and a usage count. The
+ * Msg is put on the q for each eligible Client and the Msg usage count is the
+ * number of Clients on whose q it resides. The Client write thread waits for
+ * a Msg to be on its q, performs the write, decrements the usage count and
+ * frees the Msg (and its XMLEle) if the count reaches 0. This mechanism is
+ * less valuable for Drivers since there is little need to send the same message
+ * to more than one Driver. However, a Driver slow to consume it message can
+ * block us so it still might be worth while for that reason someday.
+ *
+ * All manipulation of the Client info table, clinfo[], is guarded by client_m
+ * and client_c. All heap access is guarded by malloc_m.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#include "lilxml.h"
+#include "indiapi.h"
+#include "fq.h"
+
+#define INDIPORT 7624 /* TCP/IP port on which to listen */
+#define BUFSZ 2048 /* max buffering here */
+#define MAXDRS 4 /* default times to restart a driver */
+
+/* mutex and condition variables to guard client queue and heap access */
+static pthread_mutex_t client_m; /* client mutex */
+static pthread_cond_t client_c; /* client condition waiting for Msgs */
+static pthread_mutex_t malloc_m; /* heap mutex */
+
+/* name of a device a client is interested in */
+typedef char IDev[MAXINDIDEVICE]; /* handy array of char */
+
+/* BLOB handling, NEVER is the default */
+typedef enum {B_NEVER=0, B_ALSO, B_ONLY} BLOBEnable;
+
+/* associate a usage count with an XMLEle message */
+typedef struct {
+ XMLEle *ep; /* a message */
+ int count; /* number of consumers left */
+} Msg;
+
+/* info for each connected client */
+typedef struct {
+ int active; /* 1 when this record is in use */
+ int shutdown; /* set to close writer thread */
+ int s; /* socket for this client */
+ FILE *wfp; /* FILE to write to s */
+ BLOBEnable blob; /* when to send setBLOBs */
+ pthread_t wtid; /* writer thread id */
+ LilXML *lp; /* XML parsing context */
+ FQ *msgq; /* Msg queue */
+ IDev *devs; /* malloced array of devices we want */
+ int ndevs; /* n entries in devs[] */
+ int sawGetProperties; /* mark when see getProperties */
+} ClInfo;
+static ClInfo *clinfo; /* malloced array of clients */
+static int nclinfo; /* n total (not n active) */
+
+/* info for each connected driver */
+typedef struct {
+ char *name; /* malloced process path name */
+ IDev dev; /* device served by this driver */
+ int pid; /* process id */
+ int rfd; /* read pipe fd */
+ FILE *wfp; /* write pipe fp */
+ int restarts; /* times process has been restarted */
+ LilXML *lp; /* XML parsing context */
+} DvrInfo;
+static DvrInfo *dvrinfo; /* malloced array of drivers */
+static int ndvrinfo; /* n total */
+
+static void usage (void);
+static void *mymalloc (size_t s);
+static void *myrealloc (void *p, size_t s);
+static void myfree (void *p);
+static void noZombies (void);
+static void noSIGPIPE (void);
+static void indiRun (void);
+static void indiListen (void);
+static void newClient (void);
+static int newClSocket (void);
+static void shutdownClient (ClInfo *cp);
+static void clientMsg (ClInfo *cp);
+static void startDvr (DvrInfo *dp);
+static void restartDvr (DvrInfo *dp);
+static void send2Drivers (XMLEle *root, char *dev);
+static void send2Clients (ClInfo *notme, XMLEle *root, char *dev);
+static void addClDevice (ClInfo *cp, char *dev);
+static int findClDevice (ClInfo *cp, char *dev);
+static void driverMsg (DvrInfo *dp);
+static void *clientWThread(void *carg);
+static void freeMsg (Msg *mp);
+static BLOBEnable crackBLOB (char enableBLOB[]);
+static char *xmlLog (XMLEle *root);
+
+static char *me; /* our name */
+static int port = INDIPORT; /* public INDI port */
+static int verbose; /* more chatty */
+static int maxdrs = MAXDRS; /* max times to restart dieing driver */
+static int lsocket; /* listen socket */
+
+int
+main (int ac, char *av[])
+{
+ /* save our name */
+ me = av[0];
+
+ /* crack args */
+ while ((--ac > 0) && ((*++av)[0] == '-')) {
+ char *s;
+ for (s = av[0]+1; *s != '\0'; s++)
+ switch (*s) {
+ case 'p':
+ if (ac < 2)
+ usage();
+ port = atoi(*++av);
+ ac--;
+ break;
+ case 'r':
+ if (ac < 2)
+ usage();
+ maxdrs = atoi(*++av);
+ ac--;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ /* at this point there are ac args in av[] to name our drivers */
+ if (ac == 0)
+ usage();
+
+ /* take care of some unixisms */
+ noZombies();
+ noSIGPIPE();
+
+ /* init mutexes and condition variables */
+ pthread_mutex_init(&client_m, NULL);
+ pthread_cond_init (&client_c, NULL);
+ pthread_mutex_init(&malloc_m, NULL);
+
+ /* install our locked heap functions */
+ indi_xmlMalloc (mymalloc, myrealloc, myfree);
+ setMemFuncsFQ (mymalloc, myrealloc, myfree);
+
+ /* seed client info array so we can always use realloc */
+ clinfo = (ClInfo *) mymalloc (1);
+ nclinfo = 0;
+
+ /* create driver info array all at once so size never has to change */
+ ndvrinfo = ac;
+ dvrinfo = (DvrInfo *) mymalloc (ndvrinfo * sizeof(DvrInfo));
+ memset (dvrinfo, 0, ndvrinfo * sizeof(DvrInfo));
+
+ /* start each driver, malloc name once and keep it */
+ while (ac-- > 0) {
+ dvrinfo[ac].name = strcpy (mymalloc(strlen(*av)+1), *av);
+ startDvr (&dvrinfo[ac]);
+ av++;
+ }
+
+ /* announce we are online */
+ indiListen();
+
+ /* handle new clients and all reading */
+ while (1)
+ indiRun();
+
+ /* whoa! */
+ fprintf (stderr, "%s: unexpected return from main\n", me);
+ return (1);
+}
+
+/* print usage message and exit (1) */
+static void
+usage(void)
+{
+ fprintf (stderr, "Usage: %s [options] driver [driver ...]\n", me);
+ fprintf (stderr, "%s\n", "$Revision$");
+ fprintf (stderr, "Purpose: INDI Server\n");
+ fprintf (stderr, "Options:\n");
+ fprintf (stderr, " -p p : alternate IP port, default %d\n", INDIPORT);
+ fprintf (stderr, " -r n : max driver restarts, default %d\n", MAXDRS);
+ fprintf (stderr, " -v : show connects/disconnects, no traffic\n");
+ fprintf (stderr, " -vv : show -v + xml message root elements\n");
+ fprintf (stderr, " -vvv : show -vv + complete xml messages\n");
+
+ exit (1);
+}
+
+/* like malloc(3) but honors malloc_m mutex lock */
+static void *
+mymalloc (size_t s)
+{
+ void *mem;
+
+ pthread_mutex_lock (&malloc_m);
+ mem = malloc (s);
+ pthread_mutex_unlock (&malloc_m);
+ return (mem);
+}
+
+/* like realloc(3) but honors malloc_m mutex lock */
+static void *
+myrealloc (void *p, size_t s)
+{
+ void *mem;
+
+ pthread_mutex_lock (&malloc_m);
+ mem = realloc (p, s);
+ pthread_mutex_unlock (&malloc_m);
+ return (mem);
+}
+
+/* like free(3) but honors malloc_m mutex lock */
+static void
+myfree (void *p)
+{
+ pthread_mutex_lock (&malloc_m);
+ free (p);
+ pthread_mutex_unlock (&malloc_m);
+}
+
+/* arrange for no zombies if drivers die */
+static void
+noZombies()
+{
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+#ifdef SA_NOCLDWAIT
+ sa.sa_flags = SA_NOCLDWAIT;
+#else
+ sa.sa_flags = 0;
+#endif
+ (void)sigaction(SIGCHLD, &sa, NULL);
+}
+
+/* turn off SIGPIPE on bad write so we can handle it inline */
+static void
+noSIGPIPE()
+{
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ (void)sigaction(SIGPIPE, &sa, NULL);
+}
+
+/* start the INDI driver process using the given DvrInfo slot.
+ * exit if trouble.
+ */
+static void
+startDvr (DvrInfo *dp)
+{
+ int rp[2], wp[2];
+ int pid;
+
+ /* build two pipes for r and w */
+ if (pipe (rp) < 0) {
+ fprintf (stderr, "%s: read pipe: %s\n", me, strerror(errno));
+ exit(1);
+ }
+ if (pipe (wp) < 0) {
+ fprintf (stderr, "%s: write pipe: %s\n", me, strerror(errno));
+ exit(1);
+ }
+
+ /* fork&exec new process */
+ pid = fork();
+ if (pid < 0) {
+ fprintf (stderr, "%s: fork: %s\n", me, strerror(errno));
+ exit(1);
+ }
+ if (pid == 0) {
+ /* child: exec name */
+ int fd;
+
+ /* rig up pipes as stdin/out; stderr stays, everything else goes */
+ dup2 (wp[0], 0);
+ dup2 (rp[1], 1);
+ for (fd = 3; fd < 100; fd++)
+ (void) close (fd);
+
+ /* go -- should never return */
+ execlp (dp->name, dp->name, NULL);
+ fprintf (stderr, "Driver %s: %s\n", dp->name, strerror(errno));
+ _exit (1); /* parent will notice EOF shortly */
+ }
+
+ /* don't need child's side of pipes */
+ close (rp[1]);
+ close (wp[0]);
+
+ /* record pid, io channel, init lp */
+ dp->pid = pid;
+ dp->rfd = rp[0];
+ dp->lp = newLilXML();
+
+ /* N.B. beware implied use of malloc */
+ pthread_mutex_lock (&malloc_m);
+ dp->wfp = fdopen (wp[1], "a");
+ setbuf (dp->wfp, NULL);
+ pthread_mutex_unlock (&malloc_m);
+
+ if (verbose > 0)
+ fprintf (stderr, "Driver %s: rfd=%d wfd=%d\n", dp->name, dp->rfd,
+ wp[1]);
+}
+
+/* create the public INDI Driver endpoint lsocket on port.
+ * return server socket else exit.
+ */
+static void
+indiListen ()
+{
+ struct sockaddr_in serv_socket;
+ int sfd;
+ int reuse = 1;
+
+ /* make socket endpoint */
+ if ((sfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
+ fprintf (stderr, "%s: socket: %s", me, strerror(errno));
+ exit(1);
+ }
+
+ /* bind to given port for local IP addresses only */
+ memset (&serv_socket, 0, sizeof(serv_socket));
+ serv_socket.sin_family = AF_INET;
+ serv_socket.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+ serv_socket.sin_port = htons ((unsigned short)port);
+ if (setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0){
+ fprintf (stderr, "%s: setsockopt: %s", me, strerror(errno));
+ exit(1);
+ }
+ if (bind(sfd,(struct sockaddr*)&serv_socket,sizeof(serv_socket)) < 0){
+ fprintf (stderr, "%s: bind: %s", me, strerror(errno));
+ exit(1);
+ }
+
+ /* willing to accept connections with a backlog of 5 pending */
+ if (listen (sfd, 5) < 0) {
+ fprintf (stderr, "%s: listen: %s", me, strerror(errno));
+ exit(1);
+ }
+
+ /* ok */
+ lsocket = sfd;
+ if (verbose > 0)
+ fprintf (stderr, "%s: listening to port %d on fd %d\n",me,port,sfd);
+}
+
+/* service traffic from clients and drivers */
+static void
+indiRun(void)
+{
+ fd_set rs;
+ int maxfd;
+ int i, s;
+
+ /* start with public contact point */
+ FD_ZERO(&rs);
+ FD_SET(lsocket, &rs);
+ maxfd = lsocket;
+
+ /* add all client and driver read fd's */
+ for (i = 0; i < nclinfo; i++) {
+ ClInfo *cp = &clinfo[i];
+ if (cp->active) {
+ FD_SET(cp->s, &rs);
+ if (cp->s > maxfd)
+ maxfd = cp->s;
+ }
+ }
+ for (i = 0; i < ndvrinfo; i++) {
+ DvrInfo *dp = &dvrinfo[i];
+ FD_SET(dp->rfd, &rs);
+ if (dp->rfd > maxfd)
+ maxfd = dp->rfd;
+ }
+
+ /* wait for action */
+ s = select (maxfd+1, &rs, NULL, NULL, NULL);
+ if (s < 0) {
+ fprintf (stderr, "%s: select(%d): %s\n",me,maxfd+1,strerror(errno));
+ exit(1);
+ }
+
+ /* new client? */
+ if (s > 0 && FD_ISSET(lsocket, &rs)) {
+ newClient();
+ s -= 1;
+ }
+
+ /* message from client? */
+ for (i = 0; s > 0 && i < nclinfo; i++) {
+ if (clinfo[i].active && FD_ISSET(clinfo[i].s, &rs)) {
+ clientMsg(&clinfo[i]);
+ s -= 1;
+ }
+ }
+
+ /* message from driver? */
+ for (i = 0; s > 0 && i < ndvrinfo; i++) {
+ if (FD_ISSET(dvrinfo[i].rfd, &rs)) {
+ driverMsg(&dvrinfo[i]);
+ s -= 1;
+ }
+ }
+}
+
+/* prepare for new client arriving on lsocket.
+ * exit if trouble.
+ */
+static void
+newClient()
+{
+ ClInfo *cp = NULL;
+ int s, cli;
+
+ /* assign new socket */
+ s = newClSocket ();
+
+ /* try to reuse a clinfo slot, else add one */
+ for (cli = 0; cli < nclinfo; cli++)
+ if (!(cp = &clinfo[cli])->active)
+ break;
+ if (cli == nclinfo) {
+ /* grow clinfo, lock while moving */
+ pthread_mutex_lock (&client_m);
+ clinfo = (ClInfo *) myrealloc (clinfo, (nclinfo+1)*sizeof(ClInfo));
+ if (!clinfo) {
+ fprintf (stderr, "%s: no memory for new client\n", me);
+ exit(1);
+ }
+ cp = &clinfo[nclinfo++];
+ pthread_mutex_unlock (&client_m);
+ }
+
+ /* rig up new clinfo entry */
+ memset (cp, 0, sizeof(*cp));
+ cp->active = 1;
+ cp->s = s;
+ cp->lp = newLilXML();
+ cp->msgq = newFQ(1);
+ cp->devs = mymalloc (1);
+
+ /* N.B. beware implied use of malloc */
+ pthread_mutex_lock (&malloc_m);
+ cp->wfp = fdopen (cp->s, "a");
+ setbuf (cp->wfp, NULL);
+ pthread_mutex_unlock (&malloc_m);
+
+ if (verbose > 0)
+ fprintf (stderr, "Client %d: new arrival - welcome!\n", cp->s);
+
+ /* start the writer thread */
+ s = pthread_create (&cp->wtid, NULL, clientWThread, (void*)(cp-clinfo));
+ if (s) {
+ fprintf (stderr, "Thread create error: %s\n", strerror(s));
+ exit (1);
+ }
+}
+
+/* read more from the given client, send to each appropriate driver when see
+ * xml closure. also send all newXXX() to all other interested clients.
+ * shut down client if any trouble.
+ */
+static void
+clientMsg (ClInfo *cp)
+{
+ char buf[BUFSZ];
+ int i, nr;
+
+ /* read client */
+ nr = read (cp->s, buf, sizeof(buf));
+ if (nr < 0) {
+ fprintf (stderr, "Client %d: %s\n", cp->s, strerror(errno));
+ shutdownClient (cp);
+ return;
+ }
+ if (nr == 0) {
+ if (verbose)
+ fprintf (stderr, "Client %d: read EOF\n", cp->s);
+ shutdownClient (cp);
+ return;
+ }
+ if (verbose > 2)
+ fprintf (stderr, "Client %d: read %d:\n%.*s", cp->s, nr, nr, buf);
+
+ /* process XML, sending when find closure */
+ for (i = 0; i < nr; i++) {
+ char err[1024];
+ XMLEle *root = readXMLEle (cp->lp, buf[i], err);
+ if (root) {
+ char *roottag = tagXMLEle(root);
+
+ if (verbose > 1)
+ fprintf (stderr, "Client %d: read %s\n", cp->s,
+ xmlLog(root));
+
+ /* record BLOB message locally, others go to matching drivers */
+ if (!strcmp (roottag, "enableBLOB")) {
+ cp->blob = crackBLOB (pcdataXMLEle(root));
+ delXMLEle (root);
+ } else {
+ char *dev = findXMLAttValu (root, "device");
+
+ /* snag interested devices */
+ if (!strcmp (roottag, "getProperties"))
+ addClDevice (cp, dev);
+
+ /* send message to driver(s) responsible for dev */
+ send2Drivers (root, dev);
+
+ /* echo new* commands back to other clients, else done */
+ if (!strncmp (roottag, "new", 3))
+ send2Clients (cp, root, dev); /* does delXMLEle */
+ else
+ delXMLEle (root);
+ }
+ } else if (err[0])
+ fprintf (stderr, "Client %d: %s\n", cp->s, err);
+ }
+}
+
+/* read more from the given driver, send to each interested client when see
+ * xml closure. if driver dies, try to restarting up to MAXDRS times.
+ */
+static void
+driverMsg (DvrInfo *dp)
+{
+ char buf[BUFSZ];
+ int i, nr;
+
+ /* read driver */
+ nr = read (dp->rfd, buf, sizeof(buf));
+ if (nr < 0) {
+ fprintf (stderr, "Driver %s: %s\n", dp->name, strerror(errno));
+ restartDvr (dp);
+ return;
+ }
+ if (nr == 0) {
+ fprintf (stderr, "Driver %s: died, or failed to start\n", dp->name);
+ restartDvr (dp);
+ return;
+ }
+ if (verbose > 2)
+ fprintf (stderr,"Driver %s: read %d:\n%.*s", dp->name, nr, nr, buf);
+
+ /* process XML, sending when find closure */
+ for (i = 0; i < nr; i++) {
+ char err[1024];
+ XMLEle *root = readXMLEle (dp->lp, buf[i], err);
+ if (root) {
+ char *dev = findXMLAttValu(root,"device");
+
+ if (verbose > 1)
+ fprintf(stderr,"Driver %s: read %s\n",dp->name,xmlLog(root));
+
+ /* snag device name if not known yet */
+ if (!dp->dev[0] && dev[0]) {
+ strncpy (dp->dev, dev, sizeof(IDev)-1);
+ dp->dev[sizeof(IDev)-1] = '\0';
+ }
+
+ /* send to interested clients */
+ send2Clients (NULL, root, dev);
+ } else if (err[0])
+ fprintf (stderr, "Driver %s: %s\n", dp->name, err);
+ }
+}
+
+/* close down the given client */
+static void
+shutdownClient (ClInfo *cp)
+{
+ /* inform writer thread to exit then wait for it */
+ cp->shutdown = 1;
+ pthread_cond_broadcast (&client_c);
+ pthread_join (cp->wtid, NULL);
+
+ /* recycle this clinfo cell */
+ cp->active = 0;
+
+ if (verbose > 0)
+ fprintf (stderr, "Client %d: closed\n", cp->s);
+}
+
+/* close down the given driver and restart if not too many already */
+static void
+restartDvr (DvrInfo *dp)
+{
+ /* make sure it's dead, reclaim resources */
+ kill (dp->pid, SIGKILL);
+ fclose (dp->wfp);
+ close (dp->rfd);
+ delLilXML (dp->lp);
+
+ /* restart unless too many already */
+ if (++dp->restarts > maxdrs) {
+ fprintf (stderr, "Driver %s: died after %d restarts\n", dp->name,
+ maxdrs);
+ exit(1);
+ }
+ fprintf (stderr, "Driver %s: restart #%d\n", dp->name, dp->restarts);
+ startDvr (dp);
+}
+
+/* send the xml command to each driver supporting device dev, or all if unknown.
+ * restart if write fails.
+ */
+static void
+send2Drivers (XMLEle *root, char *dev)
+{
+ int i;
+
+ for (i = 0; i < ndvrinfo; i++) {
+ DvrInfo *dp = &dvrinfo[i];
+ if (dev[0] && dp->dev[0] && strcmp (dev, dp->dev))
+ continue;
+ prXMLEle (dp->wfp, root, 0);
+ if (ferror(dp->wfp)) {
+ fprintf (stderr, "Driver %s: %s\n", dp->name, strerror(errno));
+ restartDvr (dp);
+ } else if (verbose > 2) {
+ fprintf (stderr, "Driver %s: send:\n", dp->name);
+ prXMLEle (stderr, root, 0);
+ } else if (verbose > 1)
+ fprintf(stderr,"Driver %s: send %s\n", dp->name, xmlLog(root));
+ }
+}
+
+/* queue the xml command in root from the given device to each
+ * interested client, except notme
+ */
+static void
+send2Clients (ClInfo *notme, XMLEle *root, char *dev)
+{
+ ClInfo *cp;
+ Msg *mp;
+
+ /* build a new message */
+ mp = (Msg *) mymalloc (sizeof(Msg));
+ mp->ep = root;
+ mp->count = 0;
+
+ /* lock access to client info */
+ pthread_mutex_lock (&client_m);
+
+ /* queue message to each interested client */
+ for (cp = clinfo; cp < &clinfo[nclinfo]; cp++) {
+ int isblob;
+
+ /* cp ok? notme? valid dev? blob? */
+ if (!cp->active || cp == notme)
+ continue;
+ if (findClDevice (cp, dev) < 0)
+ continue;
+ isblob = !strcmp (tagXMLEle(root), "setBLOBVector");
+ if ((isblob && cp->blob==B_NEVER) || (!isblob && cp->blob==B_ONLY))
+ continue;
+
+ /* ok: queue message to given client */
+ mp->count++;
+ pushFQ (cp->msgq, mp);
+ }
+
+ /* wake up client write threads, the last of which will free the Msg */
+ if (mp->count > 0)
+ pthread_cond_broadcast(&client_c);
+ else {
+ if (verbose > 2)
+ fprintf (stderr, "no clients want %s\n", xmlLog(root));
+ freeMsg (mp); /* no interested clients, free Msg now */
+ }
+
+ /* finished with client info */
+ pthread_mutex_unlock (&client_m);
+}
+
+/* free Msg mp and everything it contains */
+static void
+freeMsg (Msg *mp)
+{
+ delXMLEle (mp->ep);
+ myfree (mp);
+}
+
+/* this function is the thread to perform all writes to client carg.
+ * return with client closed when we have problems or when shutdown flag is set.
+ * N.B. coordinate all access to clinfo via client_m/c.
+ * N.B. clinfo can move (be realloced) when unlocked so beware pointers thereto.
+ */
+static void *
+clientWThread(void *carg)
+{
+ int c = (int)carg;
+ ClInfo *cp;
+ Msg *mp;
+
+ /* start off wanting exclusive access to client info */
+ pthread_mutex_lock (&client_m);
+
+ /* loop until told to shut down or get write error */
+ while (1) {
+
+ /* check for message or shutdown, unlock while waiting */
+ while (nFQ(clinfo[c].msgq) == 0 && !clinfo[c].shutdown) {
+ if (verbose > 2)
+ fprintf (stderr,"Client %d: thread sleeping\n",clinfo[c].s);
+ pthread_cond_wait (&client_c, &client_m);
+ if (verbose > 2)
+ fprintf (stderr, "Client %d: thread awake\n", clinfo[c].s);
+ }
+ if (clinfo[c].shutdown)
+ break;
+
+ /* get next message for this client */
+ mp = popFQ (clinfo[c].msgq);
+
+ /* unlock client info while writing */
+ pthread_mutex_unlock (&client_m);
+ prXMLEle (clinfo[c].wfp, mp->ep, 0);
+ pthread_mutex_lock (&client_m);
+
+ /* trace */
+ cp = &clinfo[c]; /* ok to use pointer while locked */
+ if (verbose > 2) {
+ fprintf (stderr, "Client %d: send:\n", cp->s);
+ prXMLEle (stderr, mp->ep, 0);
+ } else if (verbose > 1)
+ fprintf (stderr, "Client %d: send %s\n", cp->s, xmlLog(mp->ep));
+
+ /* update message usage count, free if goes to 0 */
+ if (--mp->count == 0)
+ freeMsg (mp);
+
+ /* exit this thread if encountered write errors */
+ if (ferror(cp->wfp)) {
+ fprintf (stderr, "Client %d: %s\n", cp->s, strerror(errno));
+ break;
+ }
+ }
+
+ /* close down this client */
+ cp = &clinfo[c]; /* ok to use pointer while locked */
+ fclose (cp->wfp); /* also closes cp->s */
+ delLilXML (cp->lp);
+ myfree (cp->devs);
+
+ /* decrement and possibly free any unsent messages for this client */
+ while ((mp = (Msg*) popFQ(cp->msgq)) != NULL)
+ if (--mp->count == 0)
+ freeMsg (mp);
+ delFQ (cp->msgq);
+
+ /* this thread is now finished with client info */
+ pthread_mutex_unlock (&client_m);
+
+ /* exit thread */
+ return (0);
+}
+
+/* return 0 if we have seen getProperties from this client and dev is in its
+ * devs[] list or the list is empty, else return -1
+ */
+static int
+findClDevice (ClInfo *cp, char *dev)
+{
+ int i;
+
+ if (!cp->sawGetProperties)
+ return (-1);
+ if (cp->ndevs == 0)
+ return (0);
+ for (i = 0; i < cp->ndevs; i++)
+ if (!strncmp (dev, cp->devs[i], sizeof(IDev)-1))
+ return (0);
+ return (-1);
+}
+
+/* add the given device to the devs[] list of client cp unless empty.
+ * regardless, record having seen getProperties from this client.
+ */
+static void
+addClDevice (ClInfo *cp, char *dev)
+{
+
+ if (dev[0]) {
+ char *ip;
+ cp->devs = (IDev *) myrealloc (cp->devs,(cp->ndevs+1)*sizeof(IDev));
+ ip = (char*)&cp->devs[cp->ndevs++];
+ strncpy (ip, dev, sizeof(IDev)-1);
+ ip[sizeof(IDev)-1] = '\0';
+ }
+
+ cp->sawGetProperties = 1;
+}
+
+
+/* block to accept a new client arriving on lsocket.
+ * return private nonblocking socket or exit.
+ */
+static int
+newClSocket ()
+{
+ struct sockaddr_in cli_socket;
+ int cli_len, cli_fd;
+
+ /* get a private connection to new client */
+ cli_len = sizeof(cli_socket);
+ cli_fd = accept (lsocket, (struct sockaddr *)&cli_socket, &cli_len);
+ if(cli_fd < 0) {
+ fprintf (stderr, "%s: accept: %s", me, strerror(errno));
+ exit (1);
+ }
+
+ /* ok */
+ return (cli_fd);
+}
+
+/* convert the string value of enableBLOB to our state value */
+static BLOBEnable
+crackBLOB (char enableBLOB[])
+{
+ if (!strcmp (enableBLOB, "Also"))
+ return (B_ALSO);
+ if (!strcmp (enableBLOB, "Only"))
+ return (B_ONLY);
+ return (B_NEVER);
+}
+
+/* return pointer to static string containing tag, device and name attributes
+ * of the given xml
+ */
+static char *
+xmlLog (XMLEle *root)
+{
+ static char buf[256];
+
+ sprintf (buf, "%.*s %.*s %.*s",
+ sizeof(buf)/3-2, tagXMLEle(root),
+ sizeof(buf)/3-2, findXMLAttValu(root,"device"),
+ sizeof(buf)/3-2, findXMLAttValu(root,"name"));
+ return (buf);
+}