check and warn about capabilities misconfiguration
[collectd.git] / src / collectdctl.c
1 /**
2  * collectd - src/collectdctl.c
3  * Copyright (C) 2010 Håkon J Dugstad Johnsen
4  * Copyright (C) 2010 Sebastian Harl
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Håkon J Dugstad Johnsen <hakon-dugstad.johnsen at telenor.com>
21  *   Sebastian "tokkee" Harl <sh@tokkee.org>
22  **/
23
24 #if HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <strings.h>
33
34 #include <assert.h>
35 #include <errno.h>
36
37 #if NAN_STATIC_DEFAULT
38 # include <math.h>
39 /* #endif NAN_STATIC_DEFAULT*/
40 #elif NAN_STATIC_ISOC
41 # ifndef __USE_ISOC99
42 #  define DISABLE_ISOC99 1
43 #  define __USE_ISOC99 1
44 # endif /* !defined(__USE_ISOC99) */
45 # include <math.h>
46 # if DISABLE_ISOC99
47 #  undef DISABLE_ISOC99
48 #  undef __USE_ISOC99
49 # endif /* DISABLE_ISOC99 */
50 /* #endif NAN_STATIC_ISOC */
51 #elif NAN_ZERO_ZERO
52 # include <math.h>
53 # ifdef NAN
54 #  undef NAN
55 # endif
56 # define NAN (0.0 / 0.0)
57 # ifndef isnan
58 #  define isnan(f) ((f) != (f))
59 # endif /* !defined(isnan) */
60 # ifndef isfinite
61 #  define isfinite(f) (((f) - (f)) == 0.0)
62 # endif
63 # ifndef isinf
64 #  define isinf(f) (!isfinite(f) && !isnan(f))
65 # endif
66 #endif /* NAN_ZERO_ZERO */
67
68 #include "libcollectdclient/collectd/client.h"
69
70 #define DEFAULT_SOCK LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
71
72 extern char *optarg;
73 extern int   optind;
74
75 __attribute__((noreturn))
76 static void exit_usage (const char *name, int status) {
77   fprintf ((status == 0) ? stdout : stderr,
78       "Usage: %s [options] <command> [cmd options]\n\n"
79
80       "Available options:\n"
81       "  -s       Path to collectd's UNIX socket.\n"
82       "           Default: "DEFAULT_SOCK"\n"
83
84       "\n  -h       Display this help and exit.\n"
85
86       "\nAvailable commands:\n\n"
87
88       " * getval <identifier>\n"
89       " * flush [timeout=<seconds>] [plugin=<name>] [identifier=<id>]\n"
90       " * listval\n"
91       " * putval <identifier> [interval=<seconds>] <value-list(s)>\n"
92
93       "\nIdentifiers:\n\n"
94
95       "An identifier has the following format:\n\n"
96
97       "  [<hostname>/]<plugin>[-<plugin_instance>]/<type>[-<type_instance>]\n\n"
98
99       "Hostname defaults to the local hostname if omitted (e.g., uptime/uptime).\n"
100       "No error is returned if the specified identifier does not exist.\n"
101
102       "\n"PACKAGE_NAME" "PACKAGE_VERSION", http://collectd.org/\n"
103       "by Florian octo Forster <octo@collectd.org>\n"
104       "for contributions see `AUTHORS'\n"
105       , name);
106   exit (status);
107 }
108
109 /* Count the number of occurrences of the character 'chr'
110  * in the specified string. */
111 static int count_chars (const char *str, char chr) {
112   int count = 0;
113
114   while (*str != '\0') {
115     if (*str == chr) {
116       count++;
117     }
118     str++;
119   }
120
121   return count;
122 } /* count_chars */
123
124 static int array_grow (void **array, size_t *array_len, size_t elem_size)
125 {
126   void *tmp;
127
128   assert ((array != NULL) && (array_len != NULL));
129
130   tmp = realloc (*array, (*array_len + 1) * elem_size);
131   if (tmp == NULL) {
132     fprintf (stderr, "ERROR: Failed to allocate memory.\n");
133     return (-1);
134   }
135
136   *array = tmp;
137   ++(*array_len);
138   return (0);
139 } /* array_grow */
140
141 static int parse_identifier (lcc_connection_t *c,
142     const char *value, lcc_identifier_t *ident)
143 {
144   char hostname[1024];
145   char ident_str[1024] = "";
146   int  n_slashes;
147
148   int status;
149
150   n_slashes = count_chars (value, '/');
151   if (n_slashes == 1) {
152     /* The user has omitted the hostname part of the identifier
153      * (there is only one '/' in the identifier)
154      * Let's add the local hostname */
155     if (gethostname (hostname, sizeof (hostname)) != 0) {
156       fprintf (stderr, "ERROR: Failed to get local hostname: %s",
157           strerror (errno));
158       return (-1);
159     }
160     hostname[sizeof (hostname) - 1] = '\0';
161
162     snprintf (ident_str, sizeof (ident_str), "%s/%s", hostname, value);
163     ident_str[sizeof(ident_str) - 1] = '\0';
164   }
165   else {
166     strncpy (ident_str, value, sizeof (ident_str));
167     ident_str[sizeof (ident_str) - 1] = '\0';
168   }
169
170   status = lcc_string_to_identifier (c, ident, ident_str);
171   if (status != 0) {
172     fprintf (stderr, "ERROR: Failed to parse identifier ``%s'': %s.\n",
173         ident_str, lcc_strerror(c));
174     return (-1);
175   }
176   return (0);
177 } /* parse_identifier */
178
179 static int getval (lcc_connection_t *c, int argc, char **argv)
180 {
181   lcc_identifier_t ident;
182
183   size_t   ret_values_num   = 0;
184   gauge_t *ret_values       = NULL;
185   char   **ret_values_names = NULL;
186
187   int status;
188
189   assert (strcasecmp (argv[0], "getval") == 0);
190
191   if (argc != 2) {
192     fprintf (stderr, "ERROR: getval: Missing identifier.\n");
193     return (-1);
194   }
195
196   status = parse_identifier (c, argv[1], &ident);
197   if (status != 0)
198     return (status);
199
200 #define BAIL_OUT(s) \
201   do { \
202     if (ret_values != NULL) \
203       free (ret_values); \
204     if (ret_values_names != NULL) { \
205       for (size_t i = 0; i < ret_values_num; ++i) \
206         free (ret_values_names[i]); \
207       free (ret_values_names); \
208     } \
209     ret_values_num = 0; \
210     return (s); \
211   } while (0)
212
213   status = lcc_getval (c, &ident,
214       &ret_values_num, &ret_values, &ret_values_names);
215   if (status != 0) {
216     fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
217     BAIL_OUT (-1);
218   }
219
220   for (size_t i = 0; i < ret_values_num; ++i)
221     printf ("%s=%e\n", ret_values_names[i], ret_values[i]);
222   BAIL_OUT (0);
223 #undef BAIL_OUT
224 } /* getval */
225
226 static int flush (lcc_connection_t *c, int argc, char **argv)
227 {
228   int timeout = -1;
229
230   lcc_identifier_t *identifiers = NULL;
231   size_t identifiers_num = 0;
232
233   char **plugins = NULL;
234   size_t plugins_num = 0;
235
236   int status;
237
238   assert (strcasecmp (argv[0], "flush") == 0);
239
240 #define BAIL_OUT(s) \
241   do { \
242     if (identifiers != NULL) \
243       free (identifiers); \
244     identifiers_num = 0; \
245     if (plugins != NULL) \
246       free (plugins); \
247     plugins_num = 0; \
248     return (s); \
249   } while (0)
250
251   for (int i = 1; i < argc; ++i) {
252     char *key, *value;
253
254     key   = argv[i];
255     value = strchr (argv[i], (int)'=');
256
257     if (! value) {
258       fprintf (stderr, "ERROR: flush: Invalid option ``%s''.\n", argv[i]);
259       BAIL_OUT (-1);
260     }
261
262     *value = '\0';
263     ++value;
264
265     if (strcasecmp (key, "timeout") == 0) {
266       char *endptr = NULL;
267
268       timeout = (int) strtol (value, &endptr, 0);
269
270       if (endptr == value) {
271         fprintf (stderr, "ERROR: Failed to parse timeout as number: %s.\n",
272             value);
273         BAIL_OUT (-1);
274       }
275       else if ((endptr != NULL) && (*endptr != '\0')) {
276         fprintf (stderr, "WARNING: Ignoring trailing garbage after timeout: "
277             "%s.\n", endptr);
278       }
279     }
280     else if (strcasecmp (key, "plugin") == 0) {
281       status = array_grow ((void *)&plugins, &plugins_num,
282           sizeof (*plugins));
283       if (status != 0)
284         BAIL_OUT (status);
285
286       plugins[plugins_num - 1] = value;
287     }
288     else if (strcasecmp (key, "identifier") == 0) {
289       status = array_grow ((void *)&identifiers, &identifiers_num,
290           sizeof (*identifiers));
291       if (status != 0)
292         BAIL_OUT (status);
293
294       memset (identifiers + (identifiers_num - 1), 0, sizeof (*identifiers));
295       status = parse_identifier (c, value,
296           identifiers + (identifiers_num - 1));
297       if (status != 0)
298         BAIL_OUT (status);
299     }
300     else {
301       fprintf (stderr, "ERROR: flush: Unknown option `%s'.\n", key);
302       BAIL_OUT (-1);
303     }
304   }
305
306   if (plugins_num == 0) {
307     status = array_grow ((void *)&plugins, &plugins_num, sizeof (*plugins));
308     if (status != 0)
309       BAIL_OUT (status);
310
311     assert (plugins_num == 1);
312     plugins[0] = NULL;
313   }
314
315   for (size_t i = 0; i < plugins_num; ++i) {
316     if (identifiers_num == 0) {
317       status = lcc_flush (c, plugins[i], NULL, timeout);
318       if (status != 0)
319         fprintf (stderr, "ERROR: Failed to flush plugin `%s': %s.\n",
320             (plugins[i] == NULL) ? "(all)" : plugins[i], lcc_strerror (c));
321     }
322     else {
323       for (size_t j = 0; j < identifiers_num; ++j) {
324         status = lcc_flush (c, plugins[i], identifiers + j, timeout);
325         if (status != 0) {
326           char id[1024];
327
328           lcc_identifier_to_string (c, id, sizeof (id), identifiers + j);
329           fprintf (stderr, "ERROR: Failed to flush plugin `%s', "
330               "identifier `%s': %s.\n",
331               (plugins[i] == NULL) ? "(all)" : plugins[i],
332               id, lcc_strerror (c));
333         }
334       }
335     }
336   }
337
338   BAIL_OUT (0);
339 #undef BAIL_OUT
340 } /* flush */
341
342 static int listval (lcc_connection_t *c, int argc, char **argv)
343 {
344   lcc_identifier_t *ret_ident     = NULL;
345   size_t            ret_ident_num = 0;
346
347   int status;
348
349   assert (strcasecmp (argv[0], "listval") == 0);
350
351   if (argc != 1) {
352     fprintf (stderr, "ERROR: listval: Does not accept any arguments.\n");
353     return (-1);
354   }
355
356 #define BAIL_OUT(s) \
357   do { \
358     if (ret_ident != NULL) \
359       free (ret_ident); \
360     ret_ident_num = 0; \
361     return (s); \
362   } while (0)
363
364   status = lcc_listval (c, &ret_ident, &ret_ident_num);
365   if (status != 0) {
366     fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
367     BAIL_OUT (status);
368   }
369
370   for (size_t i = 0; i < ret_ident_num; ++i) {
371     char id[1024];
372
373     status = lcc_identifier_to_string (c, id, sizeof (id), ret_ident + i);
374     if (status != 0) {
375       fprintf (stderr, "ERROR: listval: Failed to convert returned "
376           "identifier to a string: %s\n", lcc_strerror (c));
377       continue;
378     }
379
380     printf ("%s\n", id);
381   }
382   BAIL_OUT (0);
383 #undef BAIL_OUT
384 } /* listval */
385
386 static int putval (lcc_connection_t *c, int argc, char **argv)
387 {
388   lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
389
390   /* 64 ought to be enough for anybody ;-) */
391   value_t values[64];
392   int     values_types[64];
393   size_t  values_len = 0;
394
395   int status;
396
397   assert (strcasecmp (argv[0], "putval") == 0);
398
399   if (argc < 3) {
400     fprintf (stderr, "ERROR: putval: Missing identifier "
401         "and/or value list.\n");
402     return (-1);
403   }
404
405   vl.values       = values;
406   vl.values_types = values_types;
407
408   status = parse_identifier (c, argv[1], &vl.identifier);
409   if (status != 0)
410     return (status);
411
412   for (int i = 2; i < argc; ++i) {
413     char *tmp;
414
415     tmp = strchr (argv[i], (int)'=');
416
417     if (tmp != NULL) { /* option */
418       char *key   = argv[i];
419       char *value = tmp;
420
421       *value = '\0';
422       ++value;
423
424       if (strcasecmp (key, "interval") == 0) {
425         char *endptr;
426
427         vl.interval = strtol (value, &endptr, 0);
428
429         if (endptr == value) {
430           fprintf (stderr, "ERROR: Failed to parse interval as number: %s.\n",
431               value);
432           return (-1);
433         }
434         else if ((endptr != NULL) && (*endptr != '\0')) {
435           fprintf (stderr, "WARNING: Ignoring trailing garbage after "
436               "interval: %s.\n", endptr);
437         }
438       }
439       else {
440         fprintf (stderr, "ERROR: putval: Unknown option `%s'.\n", key);
441         return (-1);
442       }
443     }
444     else { /* value list */
445       char *value;
446
447       tmp = strchr (argv[i], (int)':');
448
449       if (tmp == NULL) {
450         fprintf (stderr, "ERROR: putval: Invalid value list: %s.\n",
451             argv[i]);
452         return (-1);
453       }
454
455       *tmp = '\0';
456       ++tmp;
457
458       if (strcasecmp (argv[i], "N") == 0) {
459         vl.time = 0;
460       }
461       else {
462         char *endptr;
463
464         vl.time = strtol (argv[i], &endptr, 0);
465
466         if (endptr == argv[i]) {
467           fprintf (stderr, "ERROR: Failed to parse time as number: %s.\n",
468               argv[i]);
469           return (-1);
470         }
471         else if ((endptr != NULL) && (*endptr != '\0')) {
472           fprintf (stderr, "ERROR: Garbage after time: %s.\n", endptr);
473           return (-1);
474         }
475       }
476
477       values_len = 0;
478       value = tmp;
479       while (value != NULL) {
480         char *dot, *endptr;
481
482         tmp = strchr (value, (int)':');
483
484         if (tmp != NULL) {
485           *tmp = '\0';
486           ++tmp;
487         }
488
489         /* This is a bit of a hack, but parsing types.db just does not make
490          * much sense imho -- the server might have different types defined
491          * anyway. Also, lcc uses the type information for formatting the
492          * number only, so the real meaning does not matter. -tokkee */
493         dot = strchr (value, (int)'.');
494         endptr = NULL;
495         if (strcasecmp (value, "U") == 0) {
496           values[values_len].gauge = NAN;
497           values_types[values_len] = LCC_TYPE_GAUGE;
498         }
499         else if (dot) { /* floating point value */
500           values[values_len].gauge = strtod (value, &endptr);
501           values_types[values_len] = LCC_TYPE_GAUGE;
502         }
503         else { /* integer */
504           values[values_len].counter = (counter_t) strtoull (value, &endptr, 0);
505           values_types[values_len] = LCC_TYPE_COUNTER;
506         }
507         ++values_len;
508
509         if (endptr == value) {
510           fprintf (stderr, "ERROR: Failed to parse value as number: %s.\n",
511               argv[i]);
512           return (-1);
513         }
514         else if ((endptr != NULL) && (*endptr != '\0')) {
515           fprintf (stderr, "ERROR: Garbage after value: %s.\n", endptr);
516           return (-1);
517         }
518
519         value = tmp;
520       }
521
522       assert (values_len >= 1);
523       vl.values_len = values_len;
524
525       status = lcc_putval (c, &vl);
526       if (status != 0) {
527         fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
528         return (-1);
529       }
530     }
531   }
532
533   if (values_len == 0) {
534     fprintf (stderr, "ERROR: putval: Missing value list(s).\n");
535     return (-1);
536   }
537   return (0);
538 } /* putval */
539
540 int main (int argc, char **argv) {
541   char address[1024] = "unix:"DEFAULT_SOCK;
542
543   lcc_connection_t *c;
544
545   int status;
546
547   while (42) {
548     int opt;
549
550     opt = getopt (argc, argv, "s:h");
551
552     if (opt == -1)
553       break;
554
555     switch (opt) {
556       case 's':
557         snprintf (address, sizeof (address), "unix:%s", optarg);
558         address[sizeof (address) - 1] = '\0';
559         break;
560       case 'h':
561         exit_usage (argv[0], 0);
562       default:
563         exit_usage (argv[0], 1);
564     }
565   }
566
567   if (optind >= argc) {
568     fprintf (stderr, "%s: missing command\n", argv[0]);
569     exit_usage (argv[0], 1);
570   }
571
572   c = NULL;
573   status = lcc_connect (address, &c);
574   if (status != 0) {
575     fprintf (stderr, "ERROR: Failed to connect to daemon at %s: %s.\n",
576         address, strerror (errno));
577     return (1);
578   }
579
580   if (strcasecmp (argv[optind], "getval") == 0)
581     status = getval (c, argc - optind, argv + optind);
582   else if (strcasecmp (argv[optind], "flush") == 0)
583     status = flush (c, argc - optind, argv + optind);
584   else if (strcasecmp (argv[optind], "listval") == 0)
585     status = listval (c, argc - optind, argv + optind);
586   else if (strcasecmp (argv[optind], "putval") == 0)
587     status = putval (c, argc - optind, argv + optind);
588   else {
589     fprintf (stderr, "%s: invalid command: %s\n", argv[0], argv[optind]);
590     return (1);
591   }
592
593   LCC_DESTROY (c);
594
595   if (status != 0)
596     return (status);
597   return (0);
598 } /* main */
599
600 /* vim: set sw=2 ts=2 tw=78 expandtab : */
601