moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / kstars / kstars / indi / indiserver.c
blobfbbb29025a17c182762ef17220d874723d1d6cc9
1 #if 0
2 INDI
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
19 #endif
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.
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <signal.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <netdb.h>
43 #include "lilxml.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 */
50 typedef struct {
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 */
55 } ClInfo;
56 static ClInfo *clinfo; /* malloced array of clients */
57 static int nclinfo; /* n total (not n active) */
59 /* info for each connected driver */
60 typedef struct {
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 */
67 } DvrInfo;
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 */
94 int
95 main (int ac, char *av[])
97 /* save our name */
98 me = av[0];
100 /* crack args */
101 while ((--ac > 0) && ((*++av)[0] == '-')) {
102 char *s;
103 for (s = av[0]+1; *s != '\0'; s++)
104 switch (*s) {
105 case 'p':
106 if (ac < 2)
107 usage();
108 port = atoi(*++av);
109 ac--;
110 break;
111 case 'r':
112 if (ac < 2)
113 usage();
114 maxrs = atoi(*++av);
115 ac--;
116 break;
117 case 'v':
118 verbose++;
119 break;
120 default:
121 usage();
125 /* seed arrays so we can always use realloc */
126 clinfo = (ClInfo *) malloc (sizeof(ClInfo));
127 nclinfo = 0;
128 dvrinfo = (DvrInfo *) malloc (sizeof(DvrInfo));
129 ndvrinfo = 0;
131 /* start each driver */
132 if (ac == 0)
133 usage();
134 noZombies();
135 noSigPipe();
136 while (ac-- > 0)
137 startDvr (*av++);
139 /* announce we are online */
140 indiListen();
142 /* accept and service clients until fatal error */
143 while (1)
144 indiRun();
146 /* whoa! */
147 fprintf (stderr, "%s: unexpected return from main\n", me);
148 return (1);
151 /* print usage message and exit (1) */
152 static void
153 usage(void)
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");
163 exit (1);
166 /* arrange for no zombies if things go badly */
167 static void
168 noZombies()
170 struct sigaction sa;
171 sa.sa_handler = SIG_IGN;
172 sigemptyset(&sa.sa_mask);
173 #ifdef SA_NOCLDWAIT
174 sa.sa_flags = SA_NOCLDWAIT;
175 #else
176 sa.sa_flags = 0;
177 #endif
178 (void)sigaction(SIGCHLD, &sa, NULL);
181 /* turn off SIGPIPE on bad write so we can handle it inline */
182 static void
183 noSigPipe()
185 struct sigaction sa;
186 sa.sa_handler = SIG_IGN;
187 sigemptyset(&sa.sa_mask);
188 (void)sigaction(SIGPIPE, &sa, NULL);
191 /* start the named INDI driver process.
192 * exit if trouble.
193 * N.B. name memory assumed to persist for duration of server process.
195 static void
196 startDvr (char *name)
198 DvrInfo *dp;
199 int rp[2], wp[2];
200 int pid;
201 int i;
203 /* new pipes */
204 if (pipe (rp) < 0) {
205 fprintf (stderr, "%s: read pipe: %s\n", me, strerror(errno));
206 exit(1);
208 if (pipe (wp) < 0) {
209 fprintf (stderr, "%s: write pipe: %s\n", me, strerror(errno));
210 exit(1);
213 /* new process */
214 pid = fork();
215 if (pid < 0) {
216 fprintf (stderr, "%s: fork: %s\n", me, strerror(errno));
217 exit(1);
219 if (pid == 0) {
220 /* child: exec name */
221 int fd;
223 /* rig up pipes as stdin/out; stderr stays, everything else goes */
224 dup2 (wp[0], 0);
225 dup2 (rp[1], 1);
226 for (fd = 3; fd < 100; fd++)
227 (void) close (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))
238 break;
239 if (i == ndvrinfo) {
240 /* first time */
241 dvrinfo = (DvrInfo *) realloc(dvrinfo,(ndvrinfo+1)*sizeof(DvrInfo));
242 if (!dvrinfo) {
243 fprintf (stderr, "%s: no memory for driver %s\n", me, name);
244 exit(1);
246 dp = &dvrinfo[ndvrinfo++];
247 memset (dp, 0, sizeof(*dp));
248 if (verbose > 0)
249 fprintf (stderr, "Driver %s: starting\n", name);
250 } else {
251 /* restarting, zero out but preserve restarts */
252 int restarts;
253 dp = &dvrinfo[i];
254 restarts = dp->restarts;
255 memset (dp, 0, sizeof(*dp));
256 dp->restarts = restarts;
257 if (verbose > 0)
258 fprintf (stderr, "Driver %s: restart #%d\n", name, restarts);
261 /* record pid, name, io channel, init lp */
262 dp->pid = pid;
263 dp->name = name;
264 dp->rfd = rp[0];
265 close (rp[1]);
266 dp->wfp = fdopen (wp[1], "a");
267 setbuf (dp->wfp, NULL);
268 close (wp[0]);
269 dp->lp = newLilXML();
270 if (verbose > 0)
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.
277 static void
278 indiListen ()
280 struct sockaddr_in serv_socket;
281 int sfd;
282 int reuse = 1;
284 /* make socket endpoint */
285 if ((sfd = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
286 fprintf (stderr, "%s: socket: %s", me, strerror(errno));
287 exit(1);
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));
297 exit(1);
299 if (bind(sfd,(struct sockaddr*)&serv_socket,sizeof(serv_socket)) < 0){
300 fprintf (stderr, "%s: bind: %s", me, strerror(errno));
301 exit(1);
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));
307 exit(1);
310 /* ok */
311 lsocket = sfd;
312 if (verbose > 0)
313 fprintf (stderr, "%s: listening to port %d on fd %d\n",me,port,sfd);
316 /* service traffic from clients and drivers */
317 static void
318 indiRun(void)
320 fd_set rs;
321 int maxfd;
322 int i, s;
324 /* start with public contact point */
325 FD_ZERO(&rs);
326 FD_SET(lsocket, &rs);
327 maxfd = lsocket;
329 /* collect all client and driver read fd's */
330 for (i = 0; i < nclinfo; i++) {
331 ClInfo *cp = &clinfo[i];
332 if (cp->active) {
333 FD_SET(cp->s, &rs);
334 if (cp->s > maxfd)
335 maxfd = cp->s;
338 for (i = 0; i < ndvrinfo; i++) {
339 DvrInfo *dp = &dvrinfo[i];
340 FD_SET(dp->rfd, &rs);
341 if (dp->rfd > maxfd)
342 maxfd = dp->rfd;
345 /* wait for action */
346 s = select (maxfd+1, &rs, NULL, NULL, NULL);
347 if (s < 0) {
348 fprintf (stderr, "%s: select: %s\n", me, strerror(errno));
349 exit(1);
352 /* new client? */
353 if (s > 0 && FD_ISSET(lsocket, &rs)) {
354 newClient();
355 s -= 1;
358 /* message from client? */
359 for (i = 0; s > 0 && i < nclinfo; i++) {
360 if (clinfo[i].active && FD_ISSET(clinfo[i].s, &rs)) {
361 clientMsg(i);
362 s -= 1;
366 /* message from driver? */
367 for (i = 0; s > 0 && i < ndvrinfo; i++) {
368 if (FD_ISSET(dvrinfo[i].rfd, &rs)) {
369 driverMsg(i);
370 s -= 1;
375 /* prepare for new client arriving on lsocket.
376 * exit if trouble.
378 static void
379 newClient()
381 ClInfo *cp = NULL;
382 int s, cli;
384 /* assign new socket */
385 s = newClSocket ();
387 /* try to reuse a clinfo slot, else add one */
388 for (cli = 0; cli < nclinfo; cli++)
389 if (!(cp = &clinfo[cli])->active)
390 break;
391 if (cli == nclinfo) {
392 clinfo = (ClInfo *) realloc (clinfo, (nclinfo+1)*sizeof(ClInfo));
393 if (!clinfo) {
394 fprintf (stderr, "%s: no memory for new client\n", me);
395 exit(1);
397 cp = &clinfo[nclinfo++];
400 /* rig up new clinfo entry */
401 memset (cp, 0, sizeof(*cp));
402 cp->active = 1;
403 cp->s = s;
404 cp->wfp = fdopen (cp->s, "a");
405 setbuf (cp->wfp, NULL);
406 cp->lp = newLilXML();
408 if (verbose > 0)
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.
417 static void
418 clientMsg (int c)
420 ClInfo *cp = &clinfo[c];
421 char buf[BUFSZ];
422 int i, nr;
424 /* read client */
425 nr = read (cp->s, buf, sizeof(buf));
426 if (nr < 0) {
427 fprintf (stderr, "Client %d: %s\n", cp->s, strerror(errno));
428 closeClient (c);
429 return;
431 if (nr == 0) {
432 if (verbose)
433 fprintf (stderr, "Client %d: EOF\n", cp->s);
434 closeClient (c);
435 return;
437 if (verbose > 1)
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++) {
442 char err[1024];
443 XMLEle *root = readXMLEle (cp->lp, buf[i], err);
444 if (root) {
445 if (strncmp (tagXMLEle(root), "new", 3) == 0)
446 send2AllClients (cp, root);
447 send2AllDrivers (root);
448 delXMLEle (root);
449 } else if (err[0])
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.
458 static void
459 driverMsg (int d)
461 DvrInfo *dp = &dvrinfo[d];
462 char buf[BUFSZ];
463 int i, nr;
465 /* read driver */
466 nr = read (dp->rfd, buf, sizeof(buf));
467 if (nr < 0) {
468 fprintf (stderr, "Driver %s: %s\n", dp->name, strerror(errno));
469 restartDvr (d);
470 return;
472 if (nr == 0) {
473 fprintf (stderr, "Driver %s: died, or failed to start\n", dp->name);
474 restartDvr (d);
475 return;
477 if (verbose > 1)
478 fprintf (stderr, "Driver %s: rcv %d from:\n%.*s", dp->name, nr,
479 nr, buf);
481 /* process XML, sending when find closure */
482 for (i = 0; i < nr; i++) {
483 char err[1024];
484 XMLEle *root = readXMLEle (dp->lp, buf[i], err);
485 if (root) {
486 send2AllClients (NULL, root);
487 delXMLEle (root);
488 } else if (err[0])
489 fprintf (stderr, "Driver %s: %s\n", dp->name, err);
493 /* close down clinof[c] */
494 static void
495 closeClient (int c)
497 ClInfo *cp = &clinfo[c];
499 fclose (cp->wfp); /* also closes cp->s */
500 cp->active = 0;
501 delLilXML (cp->lp);
503 if (verbose > 0)
504 fprintf (stderr, "Client %d: closed\n", cp->s);
507 /* close down driver process dvrinfo[d] and restart if not too many already */
508 static void
509 restartDvr (int d)
511 DvrInfo *dp = &dvrinfo[d];
513 /* make sure it's dead, reclaim resources */
514 kill (dp->pid, SIGKILL);
515 fclose (dp->wfp);
516 close (dp->rfd);
517 delLilXML (dp->lp);
519 /* restart unless too many already */
520 if (++dp->restarts > maxrs) {
521 fprintf (stderr, "Driver %s: died after %d restarts\n", dp->name,
522 maxrs);
523 exit(1);
525 fprintf (stderr, "Driver %s: restart #%d\n", dp->name, dp->restarts);
526 startDvr (dp->name);
529 /* send the xml command to each driver */
530 static void
531 send2AllDrivers (XMLEle *root)
533 int i;
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));
540 restartDvr (i);
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 */
550 static void
551 send2AllClients (ClInfo *notthisone, XMLEle *root)
553 int i;
555 for (i = 0; i < nclinfo; i++) {
556 ClInfo *cp = &clinfo[i];
557 if (cp == notthisone || !cp->active)
558 continue;
559 if (fddrop(cp->s,root)) {
560 if (verbose > 2)
561 fprintf (stderr, "Client %d: channel full, dropping %s %s.%s\n",
562 cp->s, tagXMLEle(root), findXMLAttValu (root, "device"),
563 findXMLAttValu (root, "name"));
564 continue;
566 prXMLEle (cp->wfp, root, 0);
567 if (ferror(cp->wfp)) {
568 fprintf (stderr, "Client %d: %s\n", cp->s, strerror(errno));
569 closeClient (i);
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.
581 static int
582 newClSocket ()
584 struct sockaddr_in cli_socket;
585 int cli_len, cli_fd;
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);
590 if(cli_fd < 0) {
591 fprintf (stderr, "%s: accept: %s", me, strerror(errno));
592 exit (1);
595 /* ok */
596 return (cli_fd);
599 /* return 1 if the given file descriptor will not block for writing, else 0 */
600 static int
601 fdwritable (int fd)
603 struct timeval tv;
604 int maxfd;
605 fd_set ws;
607 FD_ZERO(&ws);
608 FD_SET(fd, &ws);
609 maxfd = fd;
610 tv.tv_sec = 0;
611 tv.tv_usec = 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
619 static int
620 fddrop (int fd, XMLEle *root)
622 XMLEle *ep;
624 /* ok if would not block or not a BLOB */
625 if (fdwritable(fd) || strcmp(tagXMLEle(root),"setBLOBVector"))
626 return (0);
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)
632 return (1);
634 /* ok */
635 return (0);