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