Instrument Neutral Distributed Interface INDI  2.0.2
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  */
30 typedef struct
31 {
32  char *vec; /* vector name */
33  char *one; /* one element name */
34 } INDIDef;
35 static 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 };
41 static int ndefs = 6; /* or 10 if -m */
42 
43 /* table of keyword to use in query vs name of INDI defXXX attribute */
44 typedef struct
45 {
46  char *keyword;
47  char *indiattr;
48 } INDIkwattr;
49 static 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 
55 typedef struct
56 {
57  char *d; /* device to seek */
58  char *p; /* property to seek */
59  char *e; /* element to seek */
60  int wc : 1; /* whether pattern uses wild cards */
61  int ok : 1; /* something matched this query */
62 } SearchDef;
63 static SearchDef *srchs; /* properties to look for */
64 static int nsrchs;
65 
66 static void usage(void);
67 static void crackDPE(char *spec);
68 static void addSearchDef(char *dev, char *prop, char *ele);
69 static void openINDIServer(void);
70 static void getprops(void);
71 static void listenINDI(void);
72 static int finished(void);
73 static void onAlarm(int dummy);
74 static int readServerChar(void);
75 static void findDPE(XMLEle *root);
76 static void findEle(XMLEle *root, char *dev, char *nam, char *defone, SearchDef *sp);
77 static void enableBLOBs(char *dev, char *nam);
78 static void oneBLOB(XMLEle *root, char *dev, char *nam, char *enam, char *p, int plen);
79 
80 static char *me; /* our name for usage() message */
81 static char host_def[] = "localhost"; /* default host name */
82 static char *host = host_def; /* working host name */
83 #define INDIPORT 7624 /* default port */
84 static int port = INDIPORT; /* working port number */
85 #define TIMEOUT 2 /* default timeout, secs */
86 static int timeout = TIMEOUT; /* working timeout, secs */
87 static int verbose; /* report extra info */
88 static LilXML *lillp; /* XML parser context */
89 #define WILDCARD '*' /* show all in this category */
90 static int onematch; /* only one possible match */
91 static int justvalue; /* if just one match show only value */
92 static int monitor; /* keep watching even after seen def */
93 static int directfd = -1; /* direct filedes to server, if >= 0 */
94 static FILE *svrwfp; /* FILE * to talk to server */
95 static FILE *svrrfp; /* FILE * to read from server */
96 static int wflag; /* show wo properties too */
97 
98 int 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 
219 static 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 */
251 static 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 */
267 static 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  */
281 static 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 */
319 static 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  */
351 static 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 */
382 static 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  */
398 static 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 */
416 static 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*/
436 static 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  */
487 static 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  */
535 static 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  */
545 static 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 = sprintf(fn, "%s.%s.%s%s", dev, nam, enam, format);
592  if (isz)
593  fn[i - 2] = '\0'; /* chop off .z */
594 
595  /* save */
596  fp = fopen(fn, "w");
597  if (fp)
598  {
599  if (verbose)
600  fprintf(stderr, "Wrote %s\n", fn);
601  fwrite(blob, bloblen, 1, fp);
602  fclose(fp);
603  }
604  else
605  {
606  fprintf(stderr, "%s: %s\n", fn, strerror(errno));
607  }
608 
609  /* clean up */
610  free(blob);
611 }
int from64tobits_fast(char *out, const char *in, int inlen)
Definition: base64.c:122
int errno
#define TIMEOUT
#define WILDCARD
#define INDIPORT
int main(int ac, char *av[])
#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
LilXML * newLilXML()
Create a new lilxml parser.
Definition: lilxml.cpp:150
const char * findXMLAttValu(XMLEle *ep, const char *name)
Find an XML element's attribute value.
Definition: lilxml.cpp:644
char * pcdataXMLEle(XMLEle *ep)
Return the pcdata of an XML element.
Definition: lilxml.cpp:606
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
XMLEle * nextXMLEle(XMLEle *ep, int init)
Iterate an XML element for a list of nesetd XML elements.
Definition: lilxml.cpp:555
void delXMLEle(XMLEle *ep)
delXMLEle Delete XML element.
Definition: lilxml.cpp:167
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.
char * vec
char * one
char * keyword
char * indiattr