Instrument Neutral Distributed Interface INDI 2.2.5
getINDIproperty.c
Go to the documentation of this file.
1/* connect to an INDI server and show all desired device.property.element
2 * with possible wild card * in any category.
3 * All types but BLOBs are handled from their defXXX messages. Receipt of a
4 * defBLOB sends enableBLOB then uses setBLOBVector for the value. BLOBs
5 * are stored in a file dev.nam.elem.format. only .z compression is handled.
6 * exit status: 0 at least some found, 1 some not found, 2 real trouble.
7 */
8
9#include "base64.h"
10#include "indiapi.h"
11#include "lilxml.h"
12#include "zlib.h"
13
14#include <errno.h>
15#include <math.h>
16#include <netdb.h>
17#include <signal.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21#include <time.h>
22#include <unistd.h>
23#include <netinet/in.h>
24#include <sys/types.h>
25#include <sys/socket.h>
26
27/* table of INDI definition elements, plus setBLOB.
28 * we also look for set* if -m
29 */
30typedef struct
31{
32 char *vec; /* vector name */
33 char *one; /* one element name */
34} INDIDef;
35static INDIDef defs[] = {
36 { "defTextVector", "defText" }, { "defNumberVector", "defNumber" }, { "defSwitchVector", "defSwitch" },
37 { "defLightVector", "defLight" }, { "defBLOBVector", "defBLOB" }, { "setBLOBVector", "oneBLOB" },
38 { "setTextVector", "oneText" }, { "setNumberVector", "oneNumber" }, { "setSwitchVector", "oneSwitch" },
39 { "setLightVector", "oneLight" },
40};
41static int ndefs = 6; /* or 10 if -m */
42
43/* table of keyword to use in query vs name of INDI defXXX attribute */
44typedef struct
45{
46 char *keyword;
47 char *indiattr;
49static INDIkwattr kwattr[] = {
50 { "_LABEL", "label" }, { "_GROUP", "group" }, { "_STATE", "state" },
51 { "_PERM", "perm" }, { "_TO", "timeout" }, { "_TS", "timestamp" },
52};
53#define NKWA (sizeof(kwattr) / sizeof(kwattr[0]))
54
55typedef struct
56{
57 char *d; /* device to seek */
58 char *p; /* property to seek */
59 char *e; /* element to seek */
60 char wc; /* whether pattern uses wild cards */
61 char ok; /* something matched this query */
62} SearchDef;
63static SearchDef *srchs; /* properties to look for */
64static int nsrchs;
65
66static void usage(void);
67static void crackDPE(char *spec);
68static void addSearchDef(char *dev, char *prop, char *ele);
69static void openINDIServer(void);
70static void getprops(void);
71static void listenINDI(void);
72static int finished(void);
73static void onAlarm(int dummy);
74static int readServerChar(void);
75static void findDPE(XMLEle *root);
76static void findEle(XMLEle *root, char *dev, char *nam, char *defone, SearchDef *sp);
77static void enableBLOBs(char *dev, char *nam);
78static void oneBLOB(XMLEle *root, char *dev, char *nam, char *enam, char *p, int plen);
79
80static char *me; /* our name for usage() message */
81static char host_def[] = "localhost"; /* default host name */
82static char *host = host_def; /* working host name */
83#define INDIPORT 7624 /* default port */
84static int port = INDIPORT; /* working port number */
85#define TIMEOUT 2 /* default timeout, secs */
86static int timeout = TIMEOUT; /* working timeout, secs */
87static int verbose; /* report extra info */
88static LilXML *lillp; /* XML parser context */
89#define WILDCARD '*' /* show all in this category */
90static int onematch; /* only one possible match */
91static int justvalue; /* if just one match show only value */
92static int monitor; /* keep watching even after seen def */
93static int directfd = -1; /* direct filedes to server, if >= 0 */
94static FILE *svrwfp; /* FILE * to talk to server */
95static FILE *svrrfp; /* FILE * to read from server */
96static int wflag; /* show wo properties too */
97
98int main(int ac, char *av[])
99{
100 /* save our name */
101 me = av[0];
102
103 /* crack args */
104 while (--ac && **++av == '-')
105 {
106 char *s = *av;
107 while (*++s)
108 {
109 switch (*s)
110 {
111 case '1': /* just value */
112 justvalue++;
113 break;
114 case 'd':
115 if (ac < 2)
116 {
117 fprintf(stderr, "-d requires open fileno\n");
118 usage();
119 }
120 directfd = atoi(*++av);
121 ac--;
122 break;
123 case 'h':
124 if (directfd >= 0)
125 {
126 fprintf(stderr, "Can not combine -d and -h\n");
127 usage();
128 }
129 if (ac < 2)
130 {
131 fprintf(stderr, "-h requires host name\n");
132 usage();
133 }
134 host = *++av;
135 ac--;
136 break;
137 case 'm':
138 monitor++;
139 ndefs = 10; /* include set*Vectors too */
140 break;
141 case 'p':
142 if (directfd >= 0)
143 {
144 fprintf(stderr, "Can not combine -d and -p\n");
145 usage();
146 }
147 if (ac < 2)
148 {
149 fprintf(stderr, "-p requires tcp port number\n");
150 usage();
151 }
152 port = atoi(*++av);
153 ac--;
154 break;
155 case 't':
156 if (ac < 2)
157 {
158 fprintf(stderr, "-t requires timeout\n");
159 usage();
160 }
161 timeout = atoi(*++av);
162 ac--;
163 break;
164 case 'v': /* verbose */
165 verbose++;
166 break;
167 case 'w':
168 wflag++;
169 break;
170 default:
171 fprintf(stderr, "Unknown flag: %c\n", *s);
172 usage();
173 }
174 }
175 }
176
177 /* now ac args starting with av[0] */
178 if (ac == 0)
179 av[ac++] = "*.*.*"; /* default is get everything */
180
181 /* crack each d.p.e */
182 while (ac--)
183 crackDPE(*av++);
184 onematch = nsrchs == 1 && !srchs[0].wc;
185
186 /* open connection */
187 if (directfd >= 0)
188 {
189 svrwfp = fdopen(directfd, "w");
190 svrrfp = fdopen(directfd, "r");
191 if (!svrwfp || !svrrfp)
192 {
193 fprintf(stderr, "Direct fd %d: %s\n", directfd, strerror(errno));
194 exit(1);
195 }
196 setbuf(svrrfp, NULL); /* don't absorb next guy's stuff */
197 if (verbose)
198 fprintf(stderr, "Using direct fd %d\n", directfd);
199 }
200 else
201 {
202 openINDIServer();
203 if (verbose)
204 fprintf(stderr, "Connected to %s on port %d\n", host, port);
205 }
206
207 /* build a parser context for cracking XML responses */
208 lillp = newLilXML();
209
210 /* issue getProperties */
211 getprops();
212
213 /* listen for responses, looking for d.p.e or timeout */
214 listenINDI();
215
216 return (0);
217}
218
219static void usage()
220{
221 fprintf(stderr, "Purpose: retrieve readable properties from an INDI server\n");
222 fprintf(stderr, "%s\n", GIT_TAG_STRING);
223 fprintf(stderr, "Usage: %s [options] [device.property.element ...]\n", me);
224 fprintf(stderr, " Any component may be \"*\" to match all (beware shell metacharacters).\n");
225 fprintf(stderr, " Reports all properties if none specified.\n");
226 fprintf(stderr, " BLOBs are saved in file named device.property.element.format\n");
227 fprintf(stderr, " In perl try: %s\n", "%props = split (/[=\\n]/, `getINDI`);");
228 fprintf(stderr, " Set element to one of following to return property attribute:\n");
229 for (int i = 0; i < (int)NKWA; i++)
230 fprintf(stderr, " %10s to report %s\n", kwattr[i].keyword, kwattr[i].indiattr);
231 fprintf(stderr, "Output format: output is fully qualified name=value one per line\n");
232 fprintf(stderr, " or just value if -1 and exactly one query without wildcards.\n");
233 fprintf(stderr, "Options:\n");
234 fprintf(stderr, " -1 : print just value if expecting exactly one response\n");
235 fprintf(stderr, " -d f : use file descriptor f already open to server\n");
236 fprintf(stderr, " -h h : alternate host, default is %s\n", host_def);
237 fprintf(stderr, " -m : keep monitoring for more updates\n");
238 fprintf(stderr, " -p p : alternate port, default is %d\n", INDIPORT);
239 fprintf(stderr, " -t t : max time to wait, default is %d secs\n", TIMEOUT);
240 fprintf(stderr, " -v : verbose (cumulative)\n");
241 fprintf(stderr, " -w : show write-only properties too\n");
242 fprintf(stderr, "Exit status:\n");
243 fprintf(stderr, " 0: found at least one match for each query\n");
244 fprintf(stderr, " 1: at least one query returned nothing\n");
245 fprintf(stderr, " 2: real trouble, try repeating with -v\n");
246
247 exit(2);
248}
249
250/* crack spec and add to srchs[], else exit */
251static void crackDPE(char *spec)
252{
253 char d[1024], p[1024], e[1024];
254
255 if (verbose)
256 fprintf(stderr, "looking for %s\n", spec);
257 if (sscanf(spec, "%[^.].%[^.].%[^.]", d, p, e) != 3)
258 {
259 fprintf(stderr, "Unknown format for property spec: %s\n", spec);
260 usage();
261 }
262
263 addSearchDef(d, p, e);
264}
265
266/* grow srchs[] with the new search */
267static void addSearchDef(char *dev, char *prop, char *ele)
268{
269 srchs = (SearchDef *)realloc(srchs, (nsrchs + 1) * sizeof(SearchDef));
270 srchs[nsrchs].d = strdup(dev);
271 srchs[nsrchs].p = strdup(prop);
272 srchs[nsrchs].e = strdup(ele);
273 srchs[nsrchs].wc = *dev == WILDCARD || *prop == WILDCARD || *ele == WILDCARD;
274 srchs[nsrchs].ok = 0;
275 nsrchs++;
276}
277
278/* open a connection to the given host and port.
279 * set svrwfp and svrrfp or die.
280 */
281static void openINDIServer(void)
282{
283 struct sockaddr_in serv_addr;
284 struct hostent *hp;
285 int sockfd;
286
287 /* lookup host address */
288 hp = gethostbyname(host);
289 if (!hp)
290 {
291 herror("gethostbyname");
292 exit(2);
293 }
294
295 /* create a socket to the INDI server */
296 (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
297 serv_addr.sin_family = AF_INET;
298 serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
299 serv_addr.sin_port = htons(port);
300 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
301 {
302 perror("socket");
303 exit(2);
304 }
305
306 /* connect */
307 if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
308 {
309 perror("connect");
310 exit(2);
311 }
312
313 /* prepare for line-oriented i/o with client */
314 svrwfp = fdopen(sockfd, "w");
315 svrrfp = fdopen(sockfd, "r");
316}
317
318/* issue getProperties to svrwfp, possibly constrained to one device */
319static void getprops()
320{
321 char *onedev = NULL;
322 int i;
323
324 /* find if only need one device */
325 for (i = 0; i < nsrchs; i++)
326 {
327 char *d = srchs[i].d;
328 if (*d == WILDCARD || (onedev && strcmp(d, onedev)))
329 {
330 onedev = NULL;
331 break;
332 }
333 else
334 onedev = d;
335 }
336
337 if (onedev)
338 fprintf(svrwfp, "<getProperties version='%g' device='%s'/>\n", INDIV, onedev);
339 else
340 fprintf(svrwfp, "<getProperties version='%g'/>\n", INDIV);
341 fflush(svrwfp);
342
343 if (verbose)
344 fprintf(stderr, "Queried properties from %s\n", onedev ? onedev : "*");
345}
346
347/* listen for INDI traffic on svrrfp.
348 * print matching srchs[] and return when see all.
349 * timeout and exit if any trouble.
350 */
351static void listenINDI()
352{
353 char msg[1024];
354
355 /* arrange to call onAlarm() if not seeing any more defXXX */
356 signal(SIGALRM, onAlarm);
357 alarm(timeout);
358
359 /* read from server, exit if find all requested properties */
360 while (1)
361 {
362 XMLEle *root = readXMLEle(lillp, readServerChar(), msg);
363 if (root)
364 {
365 /* found a complete XML element */
366 if (verbose > 1)
367 prXMLEle(stderr, root, 0);
368 findDPE(root);
369 if (finished() == 0)
370 exit(0); /* found all we want */
371 delXMLEle(root); /* not yet, delete and continue */
372 }
373 else if (msg[0])
374 {
375 fprintf(stderr, "Bad XML from %s/%d: %s\n", host, port, msg);
376 exit(2);
377 }
378 }
379}
380
381/* return 0 if we are sure we have everything we are looking for, else -1 */
382static int finished()
383{
384 int i;
385
386 if (monitor)
387 return (-1);
388
389 for (i = 0; i < nsrchs; i++)
390 if (srchs[i].wc || !srchs[i].ok)
391 return (-1);
392 return (0);
393}
394
395/* called after timeout seconds either because we are matching wild cards or
396 * there is something still not found
397 */
398static void onAlarm(int dummy)
399{
400 (void)dummy;
401 int trouble = 0;
402
403 for (int i = 0; i < nsrchs; i++)
404 {
405 if (!srchs[i].ok)
406 {
407 trouble = 1;
408 fprintf(stderr, "No %s.%s.%s from %s:%d\n", srchs[i].d, srchs[i].p, srchs[i].e, host, port);
409 }
410 }
411
412 exit(trouble ? 1 : 0);
413}
414
415/* read one char from svrrfp */
416static int readServerChar()
417{
418 int c = fgetc(svrrfp);
419
420 if (c == EOF)
421 {
422 if (ferror(svrrfp))
423 perror("read");
424 else
425 fprintf(stderr, "INDI server %s/%d disconnected\n", host, port);
426 exit(2);
427 }
428
429 if (verbose > 2)
430 fprintf(stderr, "Read %c\n", c);
431
432 return (c);
433}
434
435/* print value if root is any srchs[] we are looking for*/
436static void findDPE(XMLEle *root)
437{
438 int i, j;
439
440 for (i = 0; i < nsrchs; i++)
441 {
442 /* for each property we are looking for */
443 for (j = 0; j < ndefs; j++)
444 {
445 /* for each possible type */
446 if (strcmp(tagXMLEle(root), defs[j].vec) == 0)
447 {
448 /* legal defXXXVector, check device */
449 char *dev = (char *)findXMLAttValu(root, "device");
450 char *idev = srchs[i].d;
451 if (idev[0] == WILDCARD || !strcmp(dev, idev))
452 {
453 /* found device, check name */
454 char *nam = (char *)findXMLAttValu(root, "name");
455 char *iprop = srchs[i].p;
456 if (iprop[0] == WILDCARD || !strcmp(nam, iprop))
457 {
458 /* found device and name, check perm */
459 char *perm = (char *)findXMLAttValu(root, "perm");
460 if (!wflag && perm[0] && !strchr(perm, 'r'))
461 {
462 if (verbose)
463 fprintf(stderr, "%s.%s is write-only\n", dev, nam);
464 }
465 else
466 {
467 /* check elements or attr keywords */
468 if (!strcmp(defs[j].vec, "defBLOBVector"))
469 enableBLOBs(dev, nam);
470 else
471 findEle(root, dev, nam, defs[j].one, &srchs[i]);
472 if (onematch)
473 return; /* only one can match */
474 if (!strncmp(defs[j].vec, "def", 3))
475 alarm(timeout); /* reset timer if def */
476 }
477 }
478 }
479 }
480 }
481 }
482}
483
484/* print elements in root speced in sp (known to be of type defone).
485 * print just value if onematch and justvalue else fully qualified name.
486 */
487static void findEle(XMLEle *root, char *dev, char *nam, char *defone, SearchDef *sp)
488{
489 char *iele = sp->e;
490 XMLEle *ep = NULL;
491
492 /* check for attr keyword */
493 for (int i = 0; i < (int)NKWA; i++)
494 {
495 if (strcmp(iele, kwattr[i].keyword) == 0)
496 {
497 /* just print the property state, not the element values */
498 char *s = (char *)findXMLAttValu(root, kwattr[i].indiattr);
499 sp->ok = 1; /* progress */
500 if (onematch && justvalue)
501 printf("%s\n", s);
502 else
503 printf("%s.%s.%s=%s\n", dev, nam, kwattr[i].keyword, s);
504 return;
505 }
506 }
507
508 /* no special attr, look for specific element name */
509 for (ep = nextXMLEle(root, 1); ep; ep = nextXMLEle(root, 0))
510 {
511 if (!strcmp(tagXMLEle(ep), defone))
512 {
513 /* legal defXXX, check deeper */
514 char *enam = (char *)findXMLAttValu(ep, "name");
515 if (iele[0] == WILDCARD || !strcmp(enam, iele))
516 {
517 /* found it! */
518 char *p = pcdataXMLEle(ep);
519 sp->ok = 1; /* progress */
520 if (!strcmp(defone, "oneBLOB"))
521 oneBLOB(ep, dev, nam, enam, p, pcdatalenXMLEle(ep));
522 else if (onematch && justvalue)
523 printf("%s\n", p);
524 else
525 printf("%s.%s.%s=%s\n", dev, nam, enam, p);
526 if (onematch)
527 return; /* only one can match*/
528 }
529 }
530 }
531}
532
533/* send server command to svrwfp that enables blobs for the given dev nam
534 */
535static void enableBLOBs(char *dev, char *nam)
536{
537 if (verbose)
538 fprintf(stderr, "sending enableBLOB %s.%s\n", dev, nam);
539 fprintf(svrwfp, "<enableBLOB device='%s' name='%s'>Also</enableBLOB>\n", dev, nam);
540 fflush(svrwfp);
541}
542
543/* given a oneBLOB, save
544 */
545static void oneBLOB(XMLEle *root, char *dev, char *nam, char *enam, char *p, int plen)
546{
547 char *format;
548 FILE *fp;
549 int bloblen;
550 unsigned char *blob;
551 int ucs;
552 int isz;
553 char fn[128];
554 int i;
555
556 /* get uncompressed size */
557 ucs = atoi(findXMLAttValu(root, "size"));
558 if (verbose)
559 fprintf(stderr, "%s.%s.%s reports uncompressed size as %d\n", dev, nam, enam, ucs);
560
561 /* get format and length */
562 format = (char *)findXMLAttValu(root, "format");
563 isz = !strncmp(&format[strlen(format) - 2], ".z", 2);
564
565 /* decode blob from base64 in p */
566 blob = (unsigned char *)malloc(3 * plen / 4);
567 bloblen = from64tobits_fast((char *)blob, p, plen);
568 if (bloblen < 0)
569 {
570 fprintf(stderr, "%s.%s.%s bad base64\n", dev, nam, enam);
571 exit(2);
572 }
573
574 /* uncompress effectively in place if z */
575 if (isz)
576 {
577 uLong nuncomp = ucs;
578 unsigned char *uncomp = (unsigned char *)malloc(ucs);
579 int ok = uncompress(uncomp, &nuncomp, blob, bloblen);
580 if (ok != Z_OK)
581 {
582 fprintf(stderr, "%s.%s.%s uncompress error %d\n", dev, nam, enam, ok);
583 exit(2);
584 }
585 free(blob);
586 blob = uncomp;
587 bloblen = nuncomp;
588 }
589
590 /* rig up a file name from property name */
591 i = snprintf(fn, sizeof(fn), "%s.%s.%s%s", dev, nam, enam, format);
592 if (i >= (int)sizeof(fn))
593 i = sizeof(fn) - 1;
594 if (isz)
595 fn[i - 2] = '\0'; /* chop off .z */
596
597 /* save */
598 fp = fopen(fn, "w");
599 if (fp)
600 {
601 if (verbose)
602 fprintf(stderr, "Wrote %s\n", fn);
603 fwrite(blob, bloblen, 1, fp);
604 fclose(fp);
605 }
606 else
607 {
608 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
609 }
610
611 /* clean up */
612 free(blob);
613}
int from64tobits_fast(char *out, const char *in, int inlen)
Definition base64.c:122
int main()
int errno
#define TIMEOUT
#define WILDCARD
#define INDIPORT
#define NKWA
Constants and Data structure definitions for the interface to the reference INDI C API implementation...
#define INDIV
Definition indiapi.h:134
int verbose
Definition indidriver.c:49
char * me
Definition indidriver.c:50
void usage(void)
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition lilxml.cpp:644
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition lilxml.cpp:555
LilXML * newLilXML()
Create a new lilxml parser.
Definition lilxml.cpp:150
char * tagXMLEle(XMLEle *ep)
Return the tag of an XML element.
Definition lilxml.cpp:600
void prXMLEle(FILE *fp, XMLEle *ep, int level)
Print an XML element.
Definition lilxml.cpp:844
XMLEle * readXMLEle(LilXML *lp, int newc, char ynot[])
Process an XML one char at a time.
Definition lilxml.cpp:385
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition lilxml.cpp:167
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition lilxml.cpp:606
int pcdatalenXMLEle(XMLEle *ep)
Return the number of characters in pcdata in an XML element.
Definition lilxml.cpp:612
A little DOM-style library to handle parsing and processing an XML file.