Substituting colons with commas, to get comma seperated value (CSV) files.
[collectd.git] / src / common.c
1 /**
2  * collectd - src/common.c
3  * Copyright (C) 2005  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/
6  * or modify it under the terms of the GNU General Public Li-
7  * cence as published by the Free Software Foundation; either
8  * version 2 of the Licence, or any later version.
9  *
10  * This program is distributed in the hope that it will be use-
11  * ful, but WITHOUT ANY WARRANTY; without even the implied war-
12  * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13  * See the GNU General Public Licence for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * Licence along with this program; if not, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
18  * USA.
19  *
20  * Authors:
21  *   Florian octo Forster <octo at verplant.org>
22  *   Niki W. Waibel <niki.waibel@gmx.net>
23 **/
24
25 #include "common.h"
26 #include "utils_debug.h"
27
28 extern int operating_mode;
29
30 #ifdef HAVE_LIBKSTAT
31 extern kstat_ctl_t *kc;
32 #endif
33
34 #ifdef HAVE_LIBRRD
35 static char *rra_def[] =
36 {
37                 "RRA:AVERAGE:0.2:6:1500",
38                 "RRA:AVERAGE:0.1:180:1680",
39                 "RRA:AVERAGE:0.1:2160:1520",
40                 "RRA:MIN:0.2:6:1500",
41                 "RRA:MIN:0.1:180:1680",
42                 "RRA:MIN:0.1:2160:1520",
43                 "RRA:MAX:0.2:6:1500",
44                 "RRA:MAX:0.1:180:1680",
45                 "RRA:MAX:0.1:2160:1520",
46                 NULL
47 };
48 static int rra_num = 9;
49 #endif /* HAVE_LIBRRD */
50
51 void sstrncpy (char *d, const char *s, int len)
52 {
53         strncpy (d, s, len);
54         d[len - 1] = '\0';
55 }
56
57 char *sstrdup (const char *s)
58 {
59         char *r;
60
61         if (s == NULL)
62                 return (NULL);
63
64         if((r = strdup (s)) == NULL)
65         {
66                 DBG ("Not enough memory.");
67                 exit(3);
68         }
69
70         return (r);
71 }
72
73 void *smalloc (size_t size)
74 {
75         void *r;
76
77         if ((r = malloc (size)) == NULL)
78         {
79                 DBG("Not enough memory.");
80                 exit(3);
81         }
82
83         return r;
84 }
85
86 #if 0
87 void sfree (void **ptr)
88 {
89         if (ptr == NULL)
90                 return;
91
92         if (*ptr != NULL)
93                 free (*ptr);
94
95         *ptr = NULL;
96 }
97 #endif
98
99 int strsplit (char *string, char **fields, size_t size)
100 {
101         size_t i;
102         char *ptr;
103
104         i = 0;
105         ptr = string;
106         while ((fields[i] = strtok (ptr, " \t")) != NULL)
107         {
108                 ptr = NULL;
109                 i++;
110
111                 if (i >= size)
112                         break;
113         }
114
115         return (i);
116 }
117
118 int strjoin (char *dst, size_t dst_len,
119                 char **fields, size_t fields_num,
120                 const char *sep)
121 {
122         int field_len;
123         int sep_len;
124         int i;
125
126         memset (dst, '\0', dst_len);
127
128         if (fields_num <= 0)
129                 return (-1);
130
131         sep_len = 0;
132         if (sep != NULL)
133                 sep_len = strlen (sep);
134
135         for (i = 0; i < fields_num; i++)
136         {
137                 if ((i > 0) && (sep_len > 0))
138                 {
139                         if (dst_len <= sep_len)
140                                 return (-1);
141
142                         strncat (dst, sep, dst_len);
143                         dst_len -= sep_len;
144                 }
145
146                 field_len = strlen (fields[i]);
147
148                 if (dst_len <= field_len)
149                         return (-1);
150
151                 strncat (dst, fields[i], dst_len);
152                 dst_len -= field_len;
153         }
154
155         return (strlen (dst));
156 }
157
158 int strsubstitute (char *str, char c_from, char c_to)
159 {
160         int ret;
161
162         if (str == NULL)
163                 return (-1);
164
165         ret = 0;
166         while (*str != '\0')
167         {
168                 if (*str == c_from)
169                 {
170                         *str = c_to;
171                         ret++;
172                 }
173                 str++;
174         }
175
176         return (ret);
177 }
178
179 int escape_slashes (char *buf, int buf_len)
180 {
181         int i;
182
183         if (strcmp (buf, "/") == 0)
184         {
185                 if (buf_len < 5)
186                         return (-1);
187
188                 strncpy (buf, "root", buf_len);
189                 return (0);
190         }
191
192         /* Move one to the left */
193         memmove (buf, buf + 1, buf_len - 1);
194
195         for (i = 0; i < buf_len - 1; i++)
196         {
197                 if (buf[i] == '\0')
198                         break;
199                 else if (buf[i] == '/')
200                         buf[i] = '_';
201         }
202         buf[i] = '\0';
203
204         return (0);
205 }
206
207 static int check_create_dir (const char *file_orig)
208 {
209         struct stat statbuf;
210
211         char  file_copy[512];
212         char  dir[512];
213         int   dir_len = 512;
214         char *fields[16];
215         int   fields_num;
216         char *ptr;
217         int   last_is_file = 1;
218         int   len;
219         int   i;
220
221         /*
222          * Sanity checks first
223          */
224         if (file_orig == NULL)
225                 return (-1);
226
227         if ((len = strlen (file_orig)) < 1)
228                 return (-1);
229         else if (len >= 512)
230                 return (-1);
231
232         /*
233          * If `file_orig' ends in a slash the last component is a directory,
234          * otherwise it's a file. Act accordingly..
235          */
236         if (file_orig[len - 1] == '/')
237                 last_is_file = 0;
238
239         /*
240          * Create a copy for `strtok' to destroy
241          */
242         strncpy (file_copy, file_orig, 512);
243         file_copy[511] = '\0';
244
245         /*
246          * Break into components. This will eat up several slashes in a row and
247          * remove leading and trailing slashes..
248          */
249         ptr = file_copy;
250         fields_num = 0;
251         while ((fields[fields_num] = strtok (ptr, "/")) != NULL)
252         {
253                 ptr = NULL;
254                 fields_num++;
255
256                 if (fields_num >= 16)
257                         break;
258         }
259
260         /*
261          * For each component, do..
262          */
263         for (i = 0; i < (fields_num - last_is_file); i++)
264         {
265                 /*
266                  * Do not create directories that start with a dot. This
267                  * prevents `../../' attacks and other likely malicious
268                  * behavior.
269                  */
270                 if (fields[i][0] == '.')
271                 {
272                         syslog (LOG_ERR, "Cowardly refusing to create a directory that begins with a `.' (dot): `%s'", file_orig);
273                         return (-2);
274                 }
275
276                 /*
277                  * Join the components together again
278                  */
279                 if (strjoin (dir, dir_len, fields, i + 1, "/") < 0)
280                 {
281                         syslog (LOG_ERR, "strjoin failed: `%s', component #%i", file_orig, i);
282                         return (-1);
283                 }
284
285                 if (stat (dir, &statbuf) == -1)
286                 {
287                         if (errno == ENOENT)
288                         {
289                                 if (mkdir (dir, 0755) == -1)
290                                 {
291                                         syslog (LOG_ERR, "mkdir (%s): %s", dir, strerror (errno));
292                                         return (-1);
293                                 }
294                         }
295                         else
296                         {
297                                 syslog (LOG_ERR, "stat (%s): %s", dir, strerror (errno));
298                                 return (-1);
299                         }
300                 }
301                 else if (!S_ISDIR (statbuf.st_mode))
302                 {
303                         syslog (LOG_ERR, "stat (%s): Not a directory!", dir);
304                         return (-1);
305                 }
306         }
307
308         return (0);
309 }
310
311 static int log_create_file (char *filename, char **ds_def, int ds_num)
312 {
313         FILE *log;
314         int i;
315
316         log = fopen (filename, "w");
317         if (log == NULL)
318         {
319                 syslog (LOG_WARNING, "Failed to create %s: %s", filename,
320                                 strerror(errno));
321                 return (-1);
322         }
323
324         fprintf (log, "epoch");
325         for (i = 0; i < ds_num; i++)
326         {
327                 char *name;
328                 char *tmp;
329
330                 name = strchr (ds_def[i], ':');
331                 if (name == NULL)
332                 {
333                         syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
334                                         ds_def[i], filename);
335                         fclose(log);
336                         remove(filename);
337                         return (-1);
338                 }
339
340                 name += 1;
341                 tmp = strchr (name, ':');
342                 if (tmp == NULL)
343                 {
344                         syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
345                                         ds_def[i], filename);
346                         fclose(log);
347                         remove(filename);
348                         return (-1);
349                 }
350
351                 /* The `%.*s' is needed because there is no null-byte behind
352                  * the name. */
353                 fprintf(log, ",%.*s", (tmp - name), name);
354         }
355         fprintf(log, "\n");
356         fclose(log);
357
358         return 0;
359 }
360
361 static int log_update_file (char *host, char *file, char *values,
362                 char **ds_def, int ds_num)
363 {
364         char *tmp;
365         FILE *fp;
366         struct stat statbuf;
367         char full_file[1024];
368
369         /* Cook the values a bit: Substitute colons with commas */
370         strsubstitute (values, ':', ',');
371
372         /* host == NULL => local mode */
373         if (host != NULL)
374         {
375                 if (snprintf (full_file, 1024, "%s/%s", host, file) >= 1024)
376                         return (-1);
377         }
378         else
379         {
380                 if (snprintf (full_file, 1024, "%s", file) >= 1024)
381                         return (-1);
382         }
383
384         strncpy (full_file, file, 1024);
385
386         tmp = full_file + strlen (full_file) - 4;
387         assert (tmp > 0);
388
389         /* Change the filename for logfiles. */
390         if (strncmp (tmp, ".rrd", 4) == 0)
391         {
392                 time_t now;
393                 struct tm *tm;
394
395                 /* TODO: Find a way to minimize the calls to `localtime', since
396                  * they are pretty expensive.. */
397                 now = time (NULL);
398                 tm = localtime (&now);
399
400                 strftime (tmp, 1024 - (tmp - full_file), "-%Y-%m-%d", tm);
401
402                 /* `localtime(3)' returns a pointer to static data,
403                  * therefore the pointer may not be free'd. */
404         }
405         else
406                 DBG ("The filename ends with `%s' which is unexpected.", tmp);
407
408         if (stat (full_file, &statbuf) == -1)
409         {
410                 if (errno == ENOENT)
411                 {
412                         if (log_create_file (full_file, ds_def, ds_num))
413                                 return (-1);
414                 }
415                 else
416                 {
417                         syslog (LOG_ERR, "stat %s: %s", full_file, strerror (errno));
418                         return (-1);
419                 }
420         }
421         else if (!S_ISREG (statbuf.st_mode))
422         {
423                 syslog (LOG_ERR, "stat %s: Not a regular file!", full_file);
424                 return (-1);
425         }
426
427
428         fp = fopen (full_file, "a");
429         if (fp == NULL)
430         {
431                 syslog (LOG_WARNING, "Failed to append to %s: %s", full_file,
432                                 strerror(errno));
433                 return (-1);
434         }
435         fprintf(fp, "%s\n", values);
436         fclose(fp);
437
438         return (0);
439 } /* int log_update_file */
440
441 #if HAVE_LIBRRD
442 static int rrd_create_file (char *filename, char **ds_def, int ds_num)
443 {
444         char **argv;
445         int argc;
446         int i, j;
447         int status = 0;
448
449         if (check_create_dir (filename))
450                 return (-1);
451
452         argc = ds_num + rra_num + 4;
453
454         if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
455         {
456                 syslog (LOG_ERR, "rrd_create failed: %s", strerror (errno));
457                 return (-1);
458         }
459
460         argv[0] = "create";
461         argv[1] = filename;
462         argv[2] = "-s";
463         argv[3] = "10";
464
465         j = 4;
466         for (i = 0; i < ds_num; i++)
467                 argv[j++] = ds_def[i];
468         for (i = 0; i < rra_num; i++)
469                 argv[j++] = rra_def[i];
470         argv[j] = NULL;
471
472         optind = 0; /* bug in librrd? */
473         rrd_clear_error ();
474         if (rrd_create (argc, argv) == -1)
475         {
476                 syslog (LOG_ERR, "rrd_create failed: %s: %s", filename, rrd_get_error ());
477                 status = -1;
478         }
479
480         free (argv);
481
482         return (status);
483 }
484 #endif /* HAVE_LIBRRD */
485
486 int rrd_update_file (char *host, char *file, char *values,
487                 char **ds_def, int ds_num)
488 {
489 #if HAVE_LIBRRD
490         struct stat statbuf;
491         char full_file[1024];
492         char *argv[4] = { "update", full_file, values, NULL };
493 #endif /* HAVE_LIBRRD */
494
495         /* I'd rather have a function `common_update_file' to make this
496          * decission, but for that we'd need to touch all plugins.. */
497         if (operating_mode == MODE_LOG)
498                 return (log_update_file (host, file, values,
499                                         ds_def, ds_num));
500
501 #if HAVE_LIBRRD
502         /* host == NULL => local mode */
503         if (host != NULL)
504         {
505                 if (snprintf (full_file, 1024, "%s/%s", host, file) >= 1024)
506                         return (-1);
507         }
508         else
509         {
510                 if (snprintf (full_file, 1024, "%s", file) >= 1024)
511                         return (-1);
512         }
513
514         if (stat (full_file, &statbuf) == -1)
515         {
516                 if (errno == ENOENT)
517                 {
518                         if (rrd_create_file (full_file, ds_def, ds_num))
519                                 return (-1);
520                 }
521                 else
522                 {
523                         syslog (LOG_ERR, "stat %s: %s", full_file, strerror (errno));
524                         return (-1);
525                 }
526         }
527         else if (!S_ISREG (statbuf.st_mode))
528         {
529                 syslog (LOG_ERR, "stat %s: Not a regular file!", full_file);
530                 return (-1);
531         }
532
533         optind = 0; /* bug in librrd? */
534         rrd_clear_error ();
535         if (rrd_update (3, argv) == -1)
536         {
537                 syslog (LOG_WARNING, "rrd_update failed: %s: %s", full_file, rrd_get_error ());
538                 return (-1);
539         }
540         return (0);
541 /* #endif HAVE_LIBRRD */
542
543 #else
544         syslog (LOG_ERR, "`rrd_update_file' was called, but collectd isn't linked against librrd!");
545         return (-1);
546 #endif
547 }
548
549 #ifdef HAVE_LIBKSTAT
550 int get_kstat (kstat_t **ksp_ptr, char *module, int instance, char *name)
551 {
552         char ident[128];
553         
554         if (kc == NULL)
555                 return (-1);
556
557         snprintf (ident, 128, "%s,%i,%s", module, instance, name);
558         ident[127] = '\0';
559
560         if (*ksp_ptr == NULL)
561         {
562                 if ((*ksp_ptr = kstat_lookup (kc, module, instance, name)) == NULL)
563                 {
564                         syslog (LOG_ERR, "Cound not find kstat %s", ident);
565                         return (-1);
566                 }
567
568                 if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
569                 {
570                         syslog (LOG_WARNING, "kstat %s has wrong type", ident);
571                         *ksp_ptr = NULL;
572                         return (-1);
573                 }
574         }
575
576 #ifdef assert
577         assert (*ksp_ptr != NULL);
578         assert ((*ksp_ptr)->ks_type == KSTAT_TYPE_NAMED);
579 #endif
580
581         if (kstat_read (kc, *ksp_ptr, NULL) == -1)
582         {
583                 syslog (LOG_WARNING, "kstat %s could not be read", ident);
584                 return (-1);
585         }
586
587         if ((*ksp_ptr)->ks_type != KSTAT_TYPE_NAMED)
588         {
589                 syslog (LOG_WARNING, "kstat %s has wrong type", ident);
590                 return (-1);
591         }
592
593         return (0);
594 }
595
596 long long get_kstat_value (kstat_t *ksp, char *name)
597 {
598         kstat_named_t *kn;
599         long long retval = -1LL;
600
601 #ifdef assert
602         assert (ksp != NULL);
603         assert (ksp->ks_type == KSTAT_TYPE_NAMED);
604 #else
605         if (ksp == NULL)
606         {
607                 fprintf (stderr, "ERROR: %s:%i: ksp == NULL\n", __FILE__, __LINE__);
608                 return (-1LL);
609         }
610         else if (ksp->ks_type != KSTAT_TYPE_NAMED)
611         {
612                 fprintf (stderr, "ERROR: %s:%i: ksp->ks_type != KSTAT_TYPE_NAMED\n", __FILE__, __LINE__);
613                 return (-1LL);
614         }
615 #endif
616
617         if ((kn = (kstat_named_t *) kstat_data_lookup (ksp, name)) == NULL)
618                 return (retval);
619
620         if (kn->data_type == KSTAT_DATA_INT32)
621                 retval = (long long) kn->value.i32;
622         else if (kn->data_type == KSTAT_DATA_UINT32)
623                 retval = (long long) kn->value.ui32;
624         else if (kn->data_type == KSTAT_DATA_INT64)
625                 retval = (long long) kn->value.i64; /* According to ANSI C99 `long long' must hold at least 64 bits */
626         else if (kn->data_type == KSTAT_DATA_UINT64)
627                 retval = (long long) kn->value.ui64; /* XXX: Might overflow! */
628         else
629                 syslog (LOG_WARNING, "get_kstat_value: Not a numeric value: %s", name);
630                  
631         return (retval);
632 }
633 #endif /* HAVE_LIBKSTAT */