You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
879 lines
23 KiB
879 lines
23 KiB
/* 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); /* tqparent 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 tqcontains */
|
|
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);
|
|
}
|