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