Restructure the --enable-standards option.
[collectd.git] / src / libcollectdclient / client.c
1 /**
2  * libcollectdclient - src/libcollectdclient/client.c
3  * Copyright (C) 2008  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #if !defined(__GNUC__) || !__GNUC__
27 # define __attribute__(x) /**/
28 #endif
29
30 #include "lcc_features.h"
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #include <string.h>
39 #include <assert.h>
40 #include <errno.h>
41 #include <math.h>
42 #include <netdb.h>
43
44 #include "client.h"
45
46 /* NI_MAXHOST has been obsoleted by RFC 3493 which is a reason for SunOS 5.11
47  * to no longer define it. We'll use the old, RFC 2553 value here. */
48 #ifndef NI_MAXHOST
49 # define NI_MAXHOST 1025
50 #endif
51
52 /* OpenBSD doesn't have EPROTO, FreeBSD doesn't have EILSEQ. Oh what joy! */
53 #ifndef EILSEQ
54 # ifdef EPROTO
55 #  define EILSEQ EPROTO
56 # else
57 #  define EILSEQ EINVAL
58 # endif
59 #endif
60
61 /* Secure/static macros. They work like `strcpy' and `strcat', but assure null
62  * termination. They work for static buffers only, because they use `sizeof'.
63  * The `SSTRCATF' combines the functionality of `snprintf' and `strcat' which
64  * is very useful to add formatted stuff to the end of a buffer. */
65 #define SSTRCPY(d,s) do { \
66     strncpy ((d), (s), sizeof (d)); \
67     (d)[sizeof (d) - 1] = 0; \
68   } while (0)
69
70 #define SSTRCAT(d,s) do { \
71     size_t _l = strlen (d); \
72     strncpy ((d) + _l, (s), sizeof (d) - _l); \
73     (d)[sizeof (d) - 1] = 0; \
74   } while (0)
75
76 #define SSTRCATF(d, ...) do { \
77     char _b[sizeof (d)]; \
78     snprintf (_b, sizeof (_b), __VA_ARGS__); \
79     _b[sizeof (_b) - 1] = 0; \
80     SSTRCAT ((d), _b); \
81   } while (0)
82     
83
84 #define LCC_SET_ERRSTR(c, ...) do { \
85   snprintf ((c)->errbuf, sizeof ((c)->errbuf), __VA_ARGS__); \
86   (c)->errbuf[sizeof ((c)->errbuf) - 1] = 0; \
87 } while (0)
88
89 #if COLLECT_DEBUG
90 # define LCC_DEBUG(...) printf (__VA_ARGS__)
91 #else
92 # define LCC_DEBUG(...) /**/
93 #endif
94
95 /*
96  * Types
97  */
98 struct lcc_connection_s
99 {
100   FILE *fh;
101   char errbuf[1024];
102 };
103
104 struct lcc_response_s
105 {
106   int status;
107   char message[1024];
108   char **lines;
109   size_t lines_num;
110 };
111 typedef struct lcc_response_s lcc_response_t;
112
113 /*
114  * Private functions
115  */
116 static int lcc_set_errno (lcc_connection_t *c, int err) /* {{{ */
117 {
118   if (c == NULL)
119     return (-1);
120
121   strerror_r (err, c->errbuf, sizeof (c->errbuf));
122   c->errbuf[sizeof (c->errbuf) - 1] = 0;
123
124   return (0);
125 } /* }}} int lcc_set_errno */
126
127 /* lcc_strdup: Since `strdup' is an XSI extension, we provide our own version
128  * here. */
129 __attribute__((malloc, nonnull (1)))
130 static char *lcc_strdup (const char *str) /* {{{ */
131 {
132   size_t strsize;
133   char *ret;
134
135   strsize = strlen (str) + 1;
136   ret = (char *) malloc (strsize);
137   if (ret != NULL)
138     memcpy (ret, str, strsize);
139   return (ret);
140 } /* }}} char *lcc_strdup */
141
142 __attribute__((nonnull (1, 2)))
143 static char *lcc_strescape (char *dest, const char *src, size_t dest_size) /* {{{ */
144 {
145   size_t dest_pos;
146   size_t src_pos;
147
148   dest_pos = 0;
149   src_pos = 0;
150
151   assert (dest_size >= 3);
152
153   dest[dest_pos] = '"';
154   dest_pos++;
155
156   while (42)
157   {
158     if ((dest_pos == (dest_size - 2))
159         || (src[src_pos] == 0))
160       break;
161
162     if ((src[src_pos] == '"') || (src[src_pos] == '\\'))
163     {
164       /* Check if there is enough space for both characters.. */
165       if (dest_pos == (dest_size - 3))
166         break;
167
168       dest[dest_pos] = '\\';
169       dest_pos++;
170     }
171
172     dest[dest_pos] = src[src_pos];
173     dest_pos++;
174     src_pos++;
175   }
176
177   assert (dest_pos <= (dest_size - 2));
178
179   dest[dest_pos] = '"';
180   dest_pos++;
181
182   dest[dest_pos] = 0;
183   dest_pos++;
184   src_pos++;
185
186   return (dest);
187 } /* }}} char *lcc_strescape */
188
189 /* lcc_chomp: Removes all control-characters at the end of a string. */
190 static void lcc_chomp (char *str) /* {{{ */
191 {
192   size_t str_len;
193
194   str_len = strlen (str);
195   while (str_len > 0)
196   {
197     if (str[str_len - 1] >= 32)
198       break;
199     str[str_len - 1] = 0;
200     str_len--;
201   }
202 } /* }}} void lcc_chomp */
203
204 static void lcc_response_free (lcc_response_t *res) /* {{{ */
205 {
206   size_t i;
207
208   if (res == NULL)
209     return;
210
211   for (i = 0; i < res->lines_num; i++)
212     free (res->lines[i]);
213   free (res->lines);
214   res->lines = NULL;
215 } /* }}} void lcc_response_free */
216
217 static int lcc_send (lcc_connection_t *c, const char *command) /* {{{ */
218 {
219   int status;
220
221   LCC_DEBUG ("send:    --> %s\n", command);
222
223   status = fprintf (c->fh, "%s\r\n", command);
224   if (status < 0)
225   {
226     lcc_set_errno (c, errno);
227     return (-1);
228   }
229
230   return (0);
231 } /* }}} int lcc_send */
232
233 static int lcc_receive (lcc_connection_t *c, /* {{{ */
234     lcc_response_t *ret_res)
235 {
236   lcc_response_t res;
237   char *ptr;
238   char buffer[4096];
239   size_t i;
240
241   memset (&res, 0, sizeof (res));
242
243   /* Read the first line, containing the status and a message */
244   ptr = fgets (buffer, sizeof (buffer), c->fh);
245   if (ptr == NULL)
246   {
247     lcc_set_errno (c, errno);
248     return (-1);
249   }
250   lcc_chomp (buffer);
251   LCC_DEBUG ("receive: <-- %s\n", buffer);
252
253   /* Convert the leading status to an integer and make `ptr' to point to the
254    * beginning of the message. */
255   ptr = NULL;
256   errno = 0;
257   res.status = strtol (buffer, &ptr, 0);
258   if ((errno != 0) || (ptr == &buffer[0]))
259   {
260     lcc_set_errno (c, errno);
261     return (-1);
262   }
263
264   /* Skip white spaces after the status number */
265   while ((*ptr == ' ') || (*ptr == '\t'))
266     ptr++;
267
268   /* Now copy the message. */
269   strncpy (res.message, ptr, sizeof (res.message));
270   res.message[sizeof (res.message) - 1] = 0;
271
272   /* Error or no lines follow: We're done. */
273   if (res.status <= 0)
274   {
275     memcpy (ret_res, &res, sizeof (res));
276     return (0);
277   }
278
279   /* Allocate space for the char-pointers */
280   res.lines_num = (size_t) res.status;
281   res.status = 0;
282   res.lines = (char **) malloc (res.lines_num * sizeof (char *));
283   if (res.lines == NULL)
284   {
285     lcc_set_errno (c, ENOMEM);
286     return (-1);
287   }
288
289   /* Now receive all the lines */
290   for (i = 0; i < res.lines_num; i++)
291   {
292     ptr = fgets (buffer, sizeof (buffer), c->fh);
293     if (ptr == NULL)
294     {
295       lcc_set_errno (c, errno);
296       break;
297     }
298     lcc_chomp (buffer);
299     LCC_DEBUG ("receive: <-- %s\n", buffer);
300
301     res.lines[i] = lcc_strdup (buffer);
302     if (res.lines[i] == NULL)
303     {
304       lcc_set_errno (c, ENOMEM);
305       break;
306     }
307   }
308
309   /* Check if the for-loop exited with an error. */
310   if (i < res.lines_num)
311   {
312     while (i > 0)
313     {
314       i--;
315       free (res.lines[i]);
316     }
317     free (res.lines);
318     return (-1);
319   }
320
321   memcpy (ret_res, &res, sizeof (res));
322   return (0);
323 } /* }}} int lcc_receive */
324
325 static int lcc_sendreceive (lcc_connection_t *c, /* {{{ */
326     const char *command, lcc_response_t *ret_res)
327 {
328   lcc_response_t res;
329   int status;
330
331   if (c->fh == NULL)
332   {
333     lcc_set_errno (c, EBADF);
334     return (-1);
335   }
336
337   status = lcc_send (c, command);
338   if (status != 0)
339     return (status);
340
341   memset (&res, 0, sizeof (res));
342   status = lcc_receive (c, &res);
343   if (status == 0)
344     memcpy (ret_res, &res, sizeof (*ret_res));
345
346   return (status);
347 } /* }}} int lcc_sendreceive */
348
349 static int lcc_open_unixsocket (lcc_connection_t *c, const char *path) /* {{{ */
350 {
351   struct sockaddr_un sa;
352   int fd;
353   int status;
354
355   assert (c != NULL);
356   assert (c->fh == NULL);
357   assert (path != NULL);
358
359   /* Don't use PF_UNIX here, because it's broken on Mac OS X (10.4, possibly
360    * others). */
361   fd = socket (AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
362   if (fd < 0)
363   {
364     lcc_set_errno (c, errno);
365     return (-1);
366   }
367
368   memset (&sa, 0, sizeof (sa));
369   sa.sun_family = AF_UNIX;
370   strncpy (sa.sun_path, path, sizeof (sa.sun_path) - 1);
371
372   status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
373   if (status != 0)
374   {
375     lcc_set_errno (c, errno);
376     close (fd);
377     return (-1);
378   }
379
380   c->fh = fdopen (fd, "r+");
381   if (c->fh == NULL)
382   {
383     lcc_set_errno (c, errno);
384     close (fd);
385     return (-1);
386   }
387
388   return (0);
389 } /* }}} int lcc_open_unixsocket */
390
391 static int lcc_open_netsocket (lcc_connection_t *c, /* {{{ */
392     const char *addr_orig)
393 {
394   struct addrinfo ai_hints;
395   struct addrinfo *ai_res;
396   struct addrinfo *ai_ptr;
397   char addr_copy[NI_MAXHOST];
398   char *addr;
399   char *port;
400   int fd;
401   int status;
402
403   assert (c != NULL);
404   assert (c->fh == NULL);
405   assert (addr_orig != NULL);
406
407   strncpy(addr_copy, addr_orig, sizeof(addr_copy));
408   addr_copy[sizeof(addr_copy) - 1] = '\0';
409   addr = addr_copy;
410
411   memset (&ai_hints, 0, sizeof (ai_hints));
412   ai_hints.ai_flags = 0;
413 #ifdef AI_ADDRCONFIG
414   ai_hints.ai_flags |= AI_ADDRCONFIG;
415 #endif
416   ai_hints.ai_family = AF_UNSPEC;
417   ai_hints.ai_socktype = SOCK_STREAM;
418
419   port = NULL;
420   if (*addr == '[') /* IPv6+port format */
421   {
422     /* `addr' is something like "[2001:780:104:2:211:24ff:feab:26f8]:12345" */
423     addr++;
424
425     port = strchr (addr, ']');
426     if (port == NULL)
427     {
428       LCC_SET_ERRSTR (c, "malformed address: %s", addr_orig);
429       return (-1);
430     }
431     *port = 0;
432     port++;
433
434     if (*port == ':')
435       port++;
436     else if (*port == 0)
437       port = NULL;
438     else
439     {
440       LCC_SET_ERRSTR (c, "garbage after address: %s", port);
441       return (-1);
442     }
443   } /* if (*addr = ']') */
444   else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
445   {
446     port = strrchr (addr, ':');
447     if (port != NULL)
448     {
449       *port = 0;
450       port++;
451     }
452   }
453
454   ai_res = NULL;
455   status = getaddrinfo (addr,
456                         port == NULL ? LCC_DEFAULT_PORT : port,
457                         &ai_hints, &ai_res);
458   if (status != 0)
459   {
460     LCC_SET_ERRSTR (c, "getaddrinfo: %s", gai_strerror (status));
461     return (-1);
462   }
463
464   for (ai_ptr = ai_res; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
465   {
466     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
467     if (fd < 0)
468     {
469       status = errno;
470       fd = -1;
471       continue;
472     }
473
474     status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
475     if (status != 0)
476     {
477       status = errno;
478       close (fd);
479       fd = -1;
480       continue;
481     }
482
483     c->fh = fdopen (fd, "r+");
484     if (c->fh == NULL)
485     {
486       status = errno;
487       close (fd);
488       fd = -1;
489       continue;
490     }
491
492     assert (status == 0);
493     break;
494   } /* for (ai_ptr) */
495
496   if (status != 0)
497   {
498     lcc_set_errno (c, status);
499     return (-1);
500   }
501
502   return (0);
503 } /* }}} int lcc_open_netsocket */
504
505 static int lcc_open_socket (lcc_connection_t *c, const char *addr) /* {{{ */
506 {
507   int status = 0;
508
509   if (addr == NULL)
510     return (-1);
511
512   assert (c != NULL);
513   assert (c->fh == NULL);
514   assert (addr != NULL);
515
516   if (strncmp ("unix:", addr, strlen ("unix:")) == 0)
517     status = lcc_open_unixsocket (c, addr + strlen ("unix:"));
518   else if (addr[0] == '/')
519     status = lcc_open_unixsocket (c, addr);
520   else
521     status = lcc_open_netsocket (c, addr);
522
523   return (status);
524 } /* }}} int lcc_open_socket */
525
526 /*
527  * Public functions
528  */
529 unsigned int lcc_version (void) /* {{{ */
530 {
531   return (LCC_VERSION);
532 } /* }}} unsigned int lcc_version */
533
534 const char *lcc_version_string (void) /* {{{ */
535 {
536   return (LCC_VERSION_STRING);
537 } /* }}} const char *lcc_version_string */
538
539 const char *lcc_version_extra (void) /* {{{ */
540 {
541   return (LCC_VERSION_EXTRA);
542 } /* }}} const char *lcc_version_extra */
543
544 int lcc_connect (const char *address, lcc_connection_t **ret_con) /* {{{ */
545 {
546   lcc_connection_t *c;
547   int status;
548
549   if (address == NULL)
550     return (-1);
551
552   if (ret_con == NULL)
553     return (-1);
554
555   c = (lcc_connection_t *) malloc (sizeof (*c));
556   if (c == NULL)
557     return (-1);
558   memset (c, 0, sizeof (*c));
559
560   status = lcc_open_socket (c, address);
561   if (status != 0)
562   {
563     lcc_disconnect (c);
564     return (status);
565   }
566
567   *ret_con = c;
568   return (0);
569 } /* }}} int lcc_connect */
570
571 int lcc_disconnect (lcc_connection_t *c) /* {{{ */
572 {
573   if (c == NULL)
574     return (-1);
575
576   if (c->fh != NULL)
577   {
578     fclose (c->fh);
579     c->fh = NULL;
580   }
581
582   free (c);
583   return (0);
584 } /* }}} int lcc_disconnect */
585
586 int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident, /* {{{ */
587     size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names)
588 {
589   char ident_str[6 * LCC_NAME_LEN];
590   char ident_esc[12 * LCC_NAME_LEN];
591   char command[14 * LCC_NAME_LEN];
592
593   lcc_response_t res;
594   size_t   values_num;
595   gauge_t *values = NULL;
596   char   **values_names = NULL;
597
598   size_t i;
599   int status;
600
601   if (c == NULL)
602     return (-1);
603
604   if (ident == NULL)
605   {
606     lcc_set_errno (c, EINVAL);
607     return (-1);
608   }
609
610   /* Build a commend with an escaped version of the identifier string. */
611   status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
612   if (status != 0)
613     return (status);
614
615   snprintf (command, sizeof (command), "GETVAL %s",
616       lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
617   command[sizeof (command) - 1] = 0;
618
619   /* Send talk to the daemon.. */
620   status = lcc_sendreceive (c, command, &res);
621   if (status != 0)
622     return (status);
623
624   if (res.status != 0)
625   {
626     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
627     lcc_response_free (&res);
628     return (-1);
629   }
630
631   values_num = res.lines_num;
632
633 #define BAIL_OUT(e) do { \
634   lcc_set_errno (c, (e)); \
635   free (values); \
636   if (values_names != NULL) { \
637     for (i = 0; i < values_num; i++) { \
638       free (values_names[i]); \
639     } \
640   } \
641   free (values_names); \
642   lcc_response_free (&res); \
643   return (-1); \
644 } while (0)
645
646   /* If neither the values nor the names are requested, return here.. */
647   if ((ret_values == NULL) && (ret_values_names == NULL))
648   {
649     if (ret_values_num != NULL)
650       *ret_values_num = values_num;
651     lcc_response_free (&res);
652     return (0);
653   }
654
655   /* Allocate space for the values */
656   if (ret_values != NULL)
657   {
658     values = (gauge_t *) malloc (values_num * sizeof (*values));
659     if (values == NULL)
660       BAIL_OUT (ENOMEM);
661   }
662
663   if (ret_values_names != NULL)
664   {
665     values_names = (char **) calloc (values_num, sizeof (*values_names));
666     if (values_names == NULL)
667       BAIL_OUT (ENOMEM);
668   }
669
670   for (i = 0; i < res.lines_num; i++)
671   {
672     char *key;
673     char *value;
674     char *endptr;
675
676     key = res.lines[i];
677     value = strchr (key, '=');
678     if (value == NULL)
679       BAIL_OUT (EILSEQ);
680
681     *value = 0;
682     value++;
683
684     if (values != NULL)
685     {
686       endptr = NULL;
687       errno = 0;
688       values[i] = strtod (value, &endptr);
689
690       if ((endptr == value) || (errno != 0))
691         BAIL_OUT (errno);
692     }
693
694     if (values_names != NULL)
695     {
696       values_names[i] = lcc_strdup (key);
697       if (values_names[i] == NULL)
698         BAIL_OUT (ENOMEM);
699     }
700   } /* for (i = 0; i < res.lines_num; i++) */
701
702   if (ret_values_num != NULL)
703     *ret_values_num = values_num;
704   if (ret_values != NULL)
705     *ret_values = values;
706   if (ret_values_names != NULL)
707     *ret_values_names = values_names;
708
709   return (0);
710 } /* }}} int lcc_getval */
711
712 int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl) /* {{{ */
713 {
714   char ident_str[6 * LCC_NAME_LEN];
715   char ident_esc[12 * LCC_NAME_LEN];
716   char command[1024] = "";
717   lcc_response_t res;
718   int status;
719   size_t i;
720
721   if ((c == NULL) || (vl == NULL) || (vl->values_len < 1)
722       || (vl->values == NULL) || (vl->values_types == NULL))
723   {
724     lcc_set_errno (c, EINVAL);
725     return (-1);
726   }
727
728   status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str),
729       &vl->identifier);
730   if (status != 0)
731     return (status);
732
733   SSTRCATF (command, "PUTVAL %s",
734       lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
735
736   if (vl->interval > 0)
737     SSTRCATF (command, " interval=%i", vl->interval);
738
739   if (vl->time > 0)
740     SSTRCATF (command, "%u", (unsigned int) vl->time);
741   else
742     SSTRCAT (command, "N");
743
744   for (i = 0; i < vl->values_len; i++)
745   {
746     if (vl->values_types[i] == LCC_TYPE_COUNTER)
747       SSTRCATF (command, ":%"PRIu64, vl->values[i].counter);
748     else if (vl->values_types[i] == LCC_TYPE_GAUGE)
749     {
750       if (isnan (vl->values[i].gauge))
751         SSTRCPY (command, ":U");
752       else
753         SSTRCATF (command, ":%g", vl->values[i].gauge);
754     }
755   } /* for (i = 0; i < vl->values_len; i++) */
756
757   status = lcc_sendreceive (c, command, &res);
758   if (status != 0)
759     return (status);
760
761   if (res.status != 0)
762   {
763     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
764     lcc_response_free (&res);
765     return (-1);
766   }
767
768   lcc_response_free (&res);
769   return (0);
770 } /* }}} int lcc_putval */
771
772 int lcc_flush (lcc_connection_t *c, const char *plugin, /* {{{ */
773     lcc_identifier_t *ident, int timeout)
774 {
775   char command[1024] = "";
776   lcc_response_t res;
777   int status;
778
779   if (c == NULL)
780   {
781     lcc_set_errno (c, EINVAL);
782     return (-1);
783   }
784
785   SSTRCPY (command, "FLUSH");
786
787   if (timeout > 0)
788     SSTRCATF (command, " timeout=%i", timeout);
789
790   if (plugin != NULL)
791   {
792     char buffer[2 * LCC_NAME_LEN];
793     SSTRCATF (command, " plugin=%s",
794         lcc_strescape (buffer, plugin, sizeof (buffer)));
795   }
796
797   if (ident != NULL)
798   {
799     char ident_str[6 * LCC_NAME_LEN];
800     char ident_esc[12 * LCC_NAME_LEN];
801
802     status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
803     if (status != 0)
804       return (status);
805
806     SSTRCATF (command, " identifier=%s",
807         lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
808   }
809
810   status = lcc_sendreceive (c, command, &res);
811   if (status != 0)
812     return (status);
813
814   if (res.status != 0)
815   {
816     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
817     lcc_response_free (&res);
818     return (-1);
819   }
820
821   lcc_response_free (&res);
822   return (0);
823 } /* }}} int lcc_flush */
824
825 /* TODO: Implement lcc_putnotif */
826
827 int lcc_listval (lcc_connection_t *c, /* {{{ */
828     lcc_identifier_t **ret_ident, size_t *ret_ident_num)
829 {
830   lcc_response_t res;
831   size_t i;
832   int status;
833
834   lcc_identifier_t *ident;
835   size_t ident_num;
836
837   if (c == NULL)
838     return (-1);
839
840   if ((ret_ident == NULL) || (ret_ident_num == NULL))
841   {
842     lcc_set_errno (c, EINVAL);
843     return (-1);
844   }
845
846   status = lcc_sendreceive (c, "LISTVAL", &res);
847   if (status != 0)
848     return (status);
849
850   if (res.status != 0)
851   {
852     LCC_SET_ERRSTR (c, "Server error: %s", res.message);
853     lcc_response_free (&res);
854     return (-1);
855   }
856
857   ident_num = res.lines_num;
858   ident = (lcc_identifier_t *) malloc (ident_num * sizeof (*ident));
859   if (ident == NULL)
860   {
861     lcc_response_free (&res);
862     lcc_set_errno (c, ENOMEM);
863     return (-1);
864   }
865
866   for (i = 0; i < res.lines_num; i++)
867   {
868     char *time_str;
869     char *ident_str;
870
871     /* First field is the time. */
872     time_str = res.lines[i];
873
874     /* Set `ident_str' to the beginning of the second field. */
875     ident_str = time_str;
876     while ((*ident_str != ' ') && (*ident_str != '\t') && (*ident_str != 0))
877       ident_str++;
878     while ((*ident_str == ' ') || (*ident_str == '\t'))
879     {
880       *ident_str = 0;
881       ident_str++;
882     }
883
884     if (*ident_str == 0)
885     {
886       lcc_set_errno (c, EILSEQ);
887       status = -1;
888       break;
889     }
890
891     status = lcc_string_to_identifier (c, ident + i, ident_str);
892     if (status != 0)
893       break;
894   }
895
896   lcc_response_free (&res);
897
898   if (status != 0)
899   {
900     free (ident);
901     return (-1);
902   }
903
904   *ret_ident = ident;
905   *ret_ident_num = ident_num;
906
907   return (0);
908 } /* }}} int lcc_listval */
909
910 const char *lcc_strerror (lcc_connection_t *c) /* {{{ */
911 {
912   if (c == NULL)
913     return ("Invalid object");
914   return (c->errbuf);
915 } /* }}} const char *lcc_strerror */
916
917 int lcc_identifier_to_string (lcc_connection_t *c, /* {{{ */
918     char *string, size_t string_size, const lcc_identifier_t *ident)
919 {
920   if ((string == NULL) || (string_size < 6) || (ident == NULL))
921   {
922     lcc_set_errno (c, EINVAL);
923     return (-1);
924   }
925
926   if (ident->plugin_instance[0] == 0)
927   {
928     if (ident->type_instance[0] == 0)
929       snprintf (string, string_size, "%s/%s/%s",
930           ident->host,
931           ident->plugin,
932           ident->type);
933     else
934       snprintf (string, string_size, "%s/%s/%s-%s",
935           ident->host,
936           ident->plugin,
937           ident->type,
938           ident->type_instance);
939   }
940   else
941   {
942     if (ident->type_instance[0] == 0)
943       snprintf (string, string_size, "%s/%s-%s/%s",
944           ident->host,
945           ident->plugin,
946           ident->plugin_instance,
947           ident->type);
948     else
949       snprintf (string, string_size, "%s/%s-%s/%s-%s",
950           ident->host,
951           ident->plugin,
952           ident->plugin_instance,
953           ident->type,
954           ident->type_instance);
955   }
956
957   string[string_size - 1] = 0;
958   return (0);
959 } /* }}} int lcc_identifier_to_string */
960
961 int lcc_string_to_identifier (lcc_connection_t *c, /* {{{ */
962     lcc_identifier_t *ident, const char *string)
963 {
964   char *string_copy;
965   char *host;
966   char *plugin;
967   char *plugin_instance;
968   char *type;
969   char *type_instance;
970
971   string_copy = lcc_strdup (string);
972   if (string_copy == NULL)
973   {
974     lcc_set_errno (c, ENOMEM);
975     return (-1);
976   }
977
978   host = string_copy;
979   plugin = strchr (host, '/');
980   if (plugin == NULL)
981   {
982     LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
983     free (string_copy);
984     return (-1);
985   }
986   *plugin = 0;
987   plugin++;
988
989   type = strchr (plugin, '/');
990   if (type == NULL)
991   {
992     LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
993     free (string_copy);
994     return (-1);
995   }
996   *type = 0;
997   type++;
998
999   plugin_instance = strchr (plugin, '-');
1000   if (plugin_instance != NULL)
1001   {
1002     *plugin_instance = 0;
1003     plugin_instance++;
1004   }
1005
1006   type_instance = strchr (type, '-');
1007   if (type_instance != NULL)
1008   {
1009     *type_instance = 0;
1010     type_instance++;
1011   }
1012
1013   memset (ident, 0, sizeof (*ident));
1014
1015   SSTRCPY (ident->host, host);
1016   SSTRCPY (ident->plugin, plugin);
1017   if (plugin_instance != NULL)
1018     SSTRCPY (ident->plugin_instance, plugin_instance);
1019   SSTRCPY (ident->type, type);
1020   if (type_instance != NULL)
1021     SSTRCPY (ident->type_instance, type_instance);
1022
1023   free (string_copy);
1024   return (0);
1025 } /* }}} int lcc_string_to_identifier */
1026
1027 /* vim: set sw=2 sts=2 et fdm=marker : */