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