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