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