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