3 Copyright (C
) 2003-2005 Elwood C
. Downey
5 This library is free software
; you can redistribute it
and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation
; either
8 version
2.1 of the License
, or (at your option
) any later version
.
10 This library is distributed in the hope that it will be useful
,
11 but WITHOUT ANY WARRANTY
; without even the implied warranty of
12 MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE
. See the GNU
13 Lesser General Public License
for more details
.
15 You should have received a copy of the GNU Lesser General Public
16 License along with
this library
; if not, write to the Free Software
17 Foundation
, Inc
., 59 Temple Place
, Suite
330, Boston
, MA
02111-1307 USA
21 /** \file indiserver.c
22 \brief INDI Server provides data steering services among drivers and clients.
24 The server is passed an argv lists of the names of driver processes to run. Clients can come and go and will see each device reported by each driver. All newXXX() received from one Client are sent to all other Clients. Atomicity is achieved by XML parsing and printing, a bit crude. \n
26 Refer to the <a href="http://www.clearskyinstitute.com/INDI/INDI.pdf">INDI White Paper</a> for more details on the INDI Server.
37 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
45 #define INDIPORT 7624 /* TCP/IP port on which to listen */
46 #define BUFSZ 2048 /* max buffering here */
47 #define MAXRS 4 /* default times to restart a driver */
49 /* info for each connected client */
51 int active
; /* 1 when this record is in use */
52 int s
; /* socket for this client */
53 FILE *wfp
; /* FILE to write to s */
54 LilXML
*lp
; /* XML parsing context */
56 static ClInfo
*clinfo
; /* malloced array of clients */
57 static int nclinfo
; /* n total (not n active) */
59 /* info for each connected driver */
61 char *name
; /* process name */
62 int pid
; /* process id */
63 int rfd
; /* read pipe fd */
64 FILE *wfp
; /* write pipe fp */
65 int restarts
; /* times process has been restarted */
66 LilXML
*lp
; /* XML parsing context */
68 static DvrInfo
*dvrinfo
; /* malloced array of drivers */
69 static int ndvrinfo
; /* n total */
71 static void usage (void);
72 static void noZombies (void);
73 static void noSigPipe (void);
74 static void indiListen (void);
75 static void indiRun (void);
76 static void newClient (void);
77 static int newClSocket (void);
78 static void closeClient (int cl
);
79 static void clientMsg (int cl
);
80 static void startDvr (char *name
);
81 static void restartDvr (int i
);
82 static void send2AllDrivers (XMLEle
*root
);
83 static void send2AllClients (ClInfo
*notthisone
, XMLEle
*root
);
84 static void driverMsg (int dn
);
85 static int fdwritable (int fd
);
86 static int fddrop (int fd
, XMLEle
*root
);
88 static char *me
; /* our name */
89 static int port
= INDIPORT
; /* public INDI port */
90 static int verbose
; /* more chatty */
91 static int maxrs
= MAXRS
; /* max times to restart dieing driver */
92 static int lsocket
; /* listen socket */
95 main (int ac
, char *av
[])
101 while ((--ac
> 0) && ((*++av
)[0] == '-')) {
103 for (s
= av
[0]+1; *s
!= '\0'; s
++)
125 /* seed arrays so we can always use realloc */
126 clinfo
= (ClInfo
*) malloc (sizeof(ClInfo
));
128 dvrinfo
= (DvrInfo
*) malloc (sizeof(DvrInfo
));
131 /* start each driver */
139 /* announce we are online */
142 /* accept and service clients until fatal error */
147 fprintf (stderr
, "%s: unexpected return from main\n", me
);
151 /* print usage message and exit (1) */
155 fprintf (stderr
, "Usage: %s [options] [driver ...]\n", me
);
156 fprintf (stderr
, "Purpose: INDI Server\n");
157 fprintf (stderr
, "Options:\n");
158 fprintf (stderr
, " -p p : alternate IP port, default %d\n", INDIPORT
);
159 fprintf (stderr
, " -r n : max restart attempts, default %d\n", MAXRS
);
160 fprintf (stderr
, " -vv : more verbose to stderr\n");
161 fprintf (stderr
, "Remaining args are names of INDI drivers to run.\n");
166 /* arrange for no zombies if things go badly */
171 sa
.sa_handler
= SIG_IGN
;
172 sigemptyset(&sa
.sa_mask
);
174 sa
.sa_flags
= SA_NOCLDWAIT
;
178 (void)sigaction(SIGCHLD
, &sa
, NULL
);
181 /* turn off SIGPIPE on bad write so we can handle it inline */
186 sa
.sa_handler
= SIG_IGN
;
187 sigemptyset(&sa
.sa_mask
);
188 (void)sigaction(SIGPIPE
, &sa
, NULL
);
191 /* start the named INDI driver process.
193 * N.B. name memory assumed to persist for duration of server process.
196 startDvr (char *name
)
205 fprintf (stderr
, "%s: read pipe: %s\n", me
, strerror(errno
));
209 fprintf (stderr
, "%s: write pipe: %s\n", me
, strerror(errno
));
216 fprintf (stderr
, "%s: fork: %s\n", me
, strerror(errno
));
220 /* child: exec name */
223 /* rig up pipes as stdin/out; stderr stays, everything else goes */
226 for (fd
= 3; fd
< 100; fd
++)
229 /* go -- should never return */
230 execlp (name
, name
, NULL
);
231 fprintf (stderr
, "Driver %s: %s\n", name
, strerror(errno
));
232 exit (1); /* parent will notice EOF shortly */
235 /* add new or reuse if already in list */
236 for (i
= 0; i
< ndvrinfo
; i
++)
237 if (!strcmp (dvrinfo
[i
].name
, name
))
241 dvrinfo
= (DvrInfo
*) realloc(dvrinfo
,(ndvrinfo
+1)*sizeof(DvrInfo
));
243 fprintf (stderr
, "%s: no memory for driver %s\n", me
, name
);
246 dp
= &dvrinfo
[ndvrinfo
++];
247 memset (dp
, 0, sizeof(*dp
));
249 fprintf (stderr
, "Driver %s: starting\n", name
);
251 /* restarting, zero out but preserve restarts */
254 restarts
= dp
->restarts
;
255 memset (dp
, 0, sizeof(*dp
));
256 dp
->restarts
= restarts
;
258 fprintf (stderr
, "Driver %s: restart #%d\n", name
, restarts
);
261 /* record pid, name, io channel, init lp */
266 dp
->wfp
= fdopen (wp
[1], "a");
267 setbuf (dp
->wfp
, NULL
);
269 dp
->lp
= newLilXML();
271 fprintf (stderr
, "Driver %s: rfd %d wfd %d\n", name
, dp
->rfd
,wp
[1]);
274 /* create the public INDI Driver endpoint lsocket on port.
275 * return server socket else exit.
280 struct sockaddr_in serv_socket
;
284 /* make socket endpoint */
285 if ((sfd
= socket (AF_INET
, SOCK_STREAM
, 0)) < 0) {
286 fprintf (stderr
, "%s: socket: %s", me
, strerror(errno
));
290 /* bind to given port for local IP address */
291 memset (&serv_socket
, 0, sizeof(serv_socket
));
292 serv_socket
.sin_family
= AF_INET
;
293 serv_socket
.sin_addr
.s_addr
= htonl (INADDR_LOOPBACK
);
294 serv_socket
.sin_port
= htons ((unsigned short)port
);
295 if (setsockopt(sfd
,SOL_SOCKET
,SO_REUSEADDR
,&reuse
,sizeof(reuse
)) < 0){
296 fprintf (stderr
, "%s: setsockopt: %s", me
, strerror(errno
));
299 if (bind(sfd
,(struct sockaddr
*)&serv_socket
,sizeof(serv_socket
)) < 0){
300 fprintf (stderr
, "%s: bind: %s", me
, strerror(errno
));
304 /* willing to accept connections with a backlog of 5 pending */
305 if (listen (sfd
, 5) < 0) {
306 fprintf (stderr
, "%s: listen: %s", me
, strerror(errno
));
313 fprintf (stderr
, "%s: listening to port %d on fd %d\n",me
,port
,sfd
);
316 /* service traffic from clients and drivers */
324 /* start with public contact point */
326 FD_SET(lsocket
, &rs
);
329 /* collect all client and driver read fd's */
330 for (i
= 0; i
< nclinfo
; i
++) {
331 ClInfo
*cp
= &clinfo
[i
];
338 for (i
= 0; i
< ndvrinfo
; i
++) {
339 DvrInfo
*dp
= &dvrinfo
[i
];
340 FD_SET(dp
->rfd
, &rs
);
345 /* wait for action */
346 s
= select (maxfd
+1, &rs
, NULL
, NULL
, NULL
);
348 fprintf (stderr
, "%s: select: %s\n", me
, strerror(errno
));
353 if (s
> 0 && FD_ISSET(lsocket
, &rs
)) {
358 /* message from client? */
359 for (i
= 0; s
> 0 && i
< nclinfo
; i
++) {
360 if (clinfo
[i
].active
&& FD_ISSET(clinfo
[i
].s
, &rs
)) {
366 /* message from driver? */
367 for (i
= 0; s
> 0 && i
< ndvrinfo
; i
++) {
368 if (FD_ISSET(dvrinfo
[i
].rfd
, &rs
)) {
375 /* prepare for new client arriving on lsocket.
384 /* assign new socket */
387 /* try to reuse a clinfo slot, else add one */
388 for (cli
= 0; cli
< nclinfo
; cli
++)
389 if (!(cp
= &clinfo
[cli
])->active
)
391 if (cli
== nclinfo
) {
392 clinfo
= (ClInfo
*) realloc (clinfo
, (nclinfo
+1)*sizeof(ClInfo
));
394 fprintf (stderr
, "%s: no memory for new client\n", me
);
397 cp
= &clinfo
[nclinfo
++];
400 /* rig up new clinfo entry */
401 memset (cp
, 0, sizeof(*cp
));
404 cp
->wfp
= fdopen (cp
->s
, "a");
405 setbuf (cp
->wfp
, NULL
);
406 cp
->lp
= newLilXML();
409 fprintf (stderr
, "Client %d: new arrival - welcome!\n", cp
->s
);
412 /* read more from client clinfo[c], send to each driver when see xml closure.
413 * also send all newXXX() to all other clients.
414 * restart driver if not accepting commands.
415 * shut down client if gives us any trouble.
420 ClInfo
*cp
= &clinfo
[c
];
425 nr
= read (cp
->s
, buf
, sizeof(buf
));
427 fprintf (stderr
, "Client %d: %s\n", cp
->s
, strerror(errno
));
433 fprintf (stderr
, "Client %d: EOF\n", cp
->s
);
438 fprintf (stderr
, "Client %d: rcv %d from:\n%.*s", cp
->s
, nr
,nr
,buf
);
440 /* process XML, sending when find closure */
441 for (i
= 0; i
< nr
; i
++) {
443 XMLEle
*root
= readXMLEle (cp
->lp
, buf
[i
], err
);
445 if (strncmp (tagXMLEle(root
), "new", 3) == 0)
446 send2AllClients (cp
, root
);
447 send2AllDrivers (root
);
450 fprintf (stderr
, "Client %d: %s\n", cp
->s
, err
);
454 /* read more from driver dvrinfo[d], send to each client when see xml closure.
455 * if driver dies, try to restarting up to MAXRS times.
456 * if any client can not keep up, drop its connection.
461 DvrInfo
*dp
= &dvrinfo
[d
];
466 nr
= read (dp
->rfd
, buf
, sizeof(buf
));
468 fprintf (stderr
, "Driver %s: %s\n", dp
->name
, strerror(errno
));
473 fprintf (stderr
, "Driver %s: died, or failed to start\n", dp
->name
);
478 fprintf (stderr
, "Driver %s: rcv %d from:\n%.*s", dp
->name
, nr
,
481 /* process XML, sending when find closure */
482 for (i
= 0; i
< nr
; i
++) {
484 XMLEle
*root
= readXMLEle (dp
->lp
, buf
[i
], err
);
486 send2AllClients (NULL
, root
);
489 fprintf (stderr
, "Driver %s: %s\n", dp
->name
, err
);
493 /* close down clinof[c] */
497 ClInfo
*cp
= &clinfo
[c
];
499 fclose (cp
->wfp
); /* also closes cp->s */
504 fprintf (stderr
, "Client %d: closed\n", cp
->s
);
507 /* close down driver process dvrinfo[d] and restart if not too many already */
511 DvrInfo
*dp
= &dvrinfo
[d
];
513 /* make sure it's dead, reclaim resources */
514 kill (dp
->pid
, SIGKILL
);
519 /* restart unless too many already */
520 if (++dp
->restarts
> maxrs
) {
521 fprintf (stderr
, "Driver %s: died after %d restarts\n", dp
->name
,
525 fprintf (stderr
, "Driver %s: restart #%d\n", dp
->name
, dp
->restarts
);
529 /* send the xml command to each driver */
531 send2AllDrivers (XMLEle
*root
)
535 for (i
= 0; i
< ndvrinfo
; i
++) {
536 DvrInfo
*dp
= &dvrinfo
[i
];
537 prXMLEle (dp
->wfp
, root
, 0);
538 if (ferror(dp
->wfp
)) {
539 fprintf (stderr
, "Driver %s: %s\n", dp
->name
, strerror(errno
));
541 } else if (verbose
> 2) {
542 fprintf (stderr
, "Driver %s: send to:\n", dp
->name
);
543 prXMLEle (stderr
, root
, 0);
544 } else if (verbose
> 1)
545 fprintf (stderr
, "Driver %s: message sent\n", dp
->name
);
549 /* send the xml command to all writable clients, except notthisone */
551 send2AllClients (ClInfo
*notthisone
, XMLEle
*root
)
555 for (i
= 0; i
< nclinfo
; i
++) {
556 ClInfo
*cp
= &clinfo
[i
];
557 if (cp
== notthisone
|| !cp
->active
)
559 if (fddrop(cp
->s
,root
)) {
561 fprintf (stderr
, "Client %d: channel full, dropping %s %s.%s\n",
562 cp
->s
, tagXMLEle(root
), findXMLAttValu (root
, "device"),
563 findXMLAttValu (root
, "name"));
566 prXMLEle (cp
->wfp
, root
, 0);
567 if (ferror(cp
->wfp
)) {
568 fprintf (stderr
, "Client %d: %s\n", cp
->s
, strerror(errno
));
570 } else if (verbose
> 2) {
571 fprintf (stderr
, "Client %d: send to:\n", cp
->s
);
572 prXMLEle (stderr
, root
, 0);
573 } else if (verbose
> 1)
574 fprintf (stderr
, "Client %d: message sent\n", cp
->s
);
578 /* new client has arrived on lsocket.
579 * accept and return private nonblocking socket or exit.
584 struct sockaddr_in cli_socket
;
587 /* get a private connection to new client */
588 cli_len
= sizeof(cli_socket
);
589 cli_fd
= accept (lsocket
, (struct sockaddr
*)&cli_socket
, &cli_len
);
591 fprintf (stderr
, "%s: accept: %s", me
, strerror(errno
));
599 /* return 1 if the given file descriptor will not block for writing, else 0 */
613 return (select (maxfd
+1, NULL
, &ws
, NULL
, &tv
) == 1);
616 /* return 1 if the given file descriptor being considered for the given message
617 * should be dropped, else 0
620 fddrop (int fd
, XMLEle
*root
)
624 /* ok if would not block or not a BLOB */
625 if (fdwritable(fd
) || strcmp(tagXMLEle(root
),"setBLOBVector"))
628 /* drop if any BLOB vector element is >0 size */
629 for (ep
= nextXMLEle (root
, 1); ep
!= NULL
; ep
= nextXMLEle (root
, 0))
630 if (!strcmp (tagXMLEle(ep
), "oneBLOB") &&
631 atoi(findXMLAttValu(ep
,"size")) > 0)