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