Corrected many defines, moved log-mode functionality out of the `rrd_*' functions...
[collectd.git] / src / common.c
index d946cfb..0b603fa 100644 (file)
@@ -25,6 +25,8 @@
 #include "common.h"
 #include "utils_debug.h"
 
+extern int operating_mode;
+
 #ifdef HAVE_LIBKSTAT
 extern kstat_ctl_t *kc;
 #endif
@@ -46,35 +48,54 @@ static char *rra_def[] =
 static int rra_num = 9;
 #endif /* HAVE_LIBRRD */
 
-void
-sstrncpy(char *d, const char *s, int len)
+void sstrncpy (char *d, const char *s, int len)
 {
-       strncpy(d, s, len);
-       d[len - 1] = 0;
+       strncpy (d, s, len);
+       d[len - 1] = '\0';
 }
 
-char *
-sstrdup(const char *s)
+char *sstrdup (const char *s)
 {
-       char *r = strdup(s);
-       if(r == NULL) {
-               DBG("Not enough memory.");
+       char *r;
+
+       if (s == NULL)
+               return (NULL);
+
+       if((r = strdup (s)) == NULL)
+       {
+               DBG ("Not enough memory.");
                exit(3);
        }
-       return r;
+
+       return (r);
 }
 
-void *
-smalloc(size_t size)
+void *smalloc (size_t size)
 {
-       void *r = malloc(size);
-       if(r == NULL) {
+       void *r;
+
+       if ((r = malloc (size)) == NULL)
+       {
                DBG("Not enough memory.");
                exit(3);
        }
+
        return r;
 }
 
+#if 0
+void sfree (void **ptr)
+{
+       if (ptr == NULL)
+               return;
+
+       if (*ptr != NULL)
+               free (*ptr);
+
+       *ptr = NULL;
+}
+#endif
+
 int strsplit (char *string, char **fields, size_t size)
 {
        size_t i;
@@ -94,43 +115,313 @@ int strsplit (char *string, char **fields, size_t size)
        return (i);
 }
 
-#ifdef HAVE_LIBRRD
-int check_create_dir (char *dir)
+int strjoin (char *dst, size_t dst_len,
+               char **fields, size_t fields_num,
+               const char *sep)
+{
+       int field_len;
+       int sep_len;
+       int i;
+
+       memset (dst, '\0', dst_len);
+
+       if (fields_num <= 0)
+               return (-1);
+
+       sep_len = 0;
+       if (sep != NULL)
+               sep_len = strlen (sep);
+
+       for (i = 0; i < fields_num; i++)
+       {
+               if ((i > 0) && (sep_len > 0))
+               {
+                       if (dst_len <= sep_len)
+                               return (-1);
+
+                       strncat (dst, sep, dst_len);
+                       dst_len -= sep_len;
+               }
+
+               field_len = strlen (fields[i]);
+
+               if (dst_len <= field_len)
+                       return (-1);
+
+               strncat (dst, fields[i], dst_len);
+               dst_len -= field_len;
+       }
+
+       return (strlen (dst));
+}
+
+int escape_slashes (char *buf, int buf_len)
+{
+       int i;
+
+       if (strcmp (buf, "/") == 0)
+       {
+               if (buf_len < 5)
+                       return (-1);
+
+               strncpy (buf, "root", buf_len);
+               return (0);
+       }
+
+       /* Move one to the left */
+       memmove (buf, buf + 1, buf_len - 1);
+
+       for (i = 0; i < buf_len - 1; i++)
+       {
+               if (buf[i] == '\0')
+                       break;
+               else if (buf[i] == '/')
+                       buf[i] = '_';
+       }
+       buf[i] = '\0';
+
+       return (0);
+}
+
+static int check_create_dir (const char *file_orig)
 {
        struct stat statbuf;
 
-       if (stat (dir, &statbuf) == -1)
+       char  file_copy[512];
+       char  dir[512];
+       int   dir_len = 512;
+       char *fields[16];
+       int   fields_num;
+       char *ptr;
+       int   last_is_file = 1;
+       int   len;
+       int   i;
+
+       /*
+        * Sanity checks first
+        */
+       if (file_orig == NULL)
+               return (-1);
+
+       if ((len = strlen (file_orig)) < 1)
+               return (-1);
+       else if (len >= 512)
+               return (-1);
+
+       /*
+        * If `file_orig' ends in a slash the last component is a directory,
+        * otherwise it's a file. Act accordingly..
+        */
+       if (file_orig[len - 1] == '/')
+               last_is_file = 0;
+
+       /*
+        * Create a copy for `strtok' to destroy
+        */
+       strncpy (file_copy, file_orig, 512);
+       file_copy[511] = '\0';
+
+       /*
+        * Break into components. This will eat up several slashes in a row and
+        * remove leading and trailing slashes..
+        */
+       ptr = file_copy;
+       fields_num = 0;
+       while ((fields[fields_num] = strtok (ptr, "/")) != NULL)
        {
-               if (errno == ENOENT)
+               ptr = NULL;
+               fields_num++;
+
+               if (fields_num >= 16)
+                       break;
+       }
+
+       /*
+        * For each component, do..
+        */
+       for (i = 0; i < (fields_num - last_is_file); i++)
+       {
+               /*
+                * Do not create directories that start with a dot. This
+                * prevents `../../' attacks and other likely malicious
+                * behavior.
+                */
+               if (fields[i][0] == '.')
+               {
+                       syslog (LOG_ERR, "Cowardly refusing to create a directory that begins with a `.' (dot): `%s'", file_orig);
+                       return (-2);
+               }
+
+               /*
+                * Join the components together again
+                */
+               if (strjoin (dir, dir_len, fields, i + 1, "/") < 0)
                {
-                       if (mkdir (dir, 0755) == -1)
+                       syslog (LOG_ERR, "strjoin failed: `%s', component #%i", file_orig, i);
+                       return (-1);
+               }
+
+               if (stat (dir, &statbuf) == -1)
+               {
+                       if (errno == ENOENT)
+                       {
+                               if (mkdir (dir, 0755) == -1)
+                               {
+                                       syslog (LOG_ERR, "mkdir (%s): %s", dir, strerror (errno));
+                                       return (-1);
+                               }
+                       }
+                       else
                        {
-                               syslog (LOG_ERR, "mkdir %s: %s", dir, strerror (errno));
+                               syslog (LOG_ERR, "stat (%s): %s", dir, strerror (errno));
                                return (-1);
                        }
                }
+               else if (!S_ISDIR (statbuf.st_mode))
+               {
+                       syslog (LOG_ERR, "stat (%s): Not a directory!", dir);
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+static int log_create_file (char *filename, char **ds_def, int ds_num)
+{
+       FILE *log;
+       int i;
+
+       log = fopen (filename, "w");
+       if (log == NULL)
+       {
+               syslog (LOG_WARNING, "Failed to create %s: %s", filename,
+                               strerror(errno));
+               return (-1);
+       }
+
+       for (i = 0; i < ds_num; i++)
+       {
+               char *name;
+               char *tmp;
+
+               name = index (ds_def[i], ':');
+               if (name == NULL)
+               {
+                       syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
+                                       ds_def[i], filename);
+                       fclose(log);
+                       remove(filename);
+                       return (-1);
+               }
+
+               name += 1;
+               tmp = index(name, ':');
+               if (tmp == NULL)
+               {
+                       syslog (LOG_WARNING, "Invalid DS definition '%s' for %s",
+                                       ds_def[i], filename);
+                       fclose(log);
+                       remove(filename);
+                       return (-1);
+               }
+
+               if (i != 0)
+                       fprintf (log, ":");
+               fprintf(log, "%.*s", (tmp - name), name);
+       }
+       fprintf(log, "\n");
+       fclose(log);
+
+       return 0;
+}
+
+static int log_update_file (char *host, char *file, char *values,
+               char **ds_def, int ds_num)
+{
+       char *tmp;
+       FILE *fp;
+       struct stat statbuf;
+       char full_file[1024];
+
+       /* host == NULL => local mode */
+       if (host != NULL)
+       {
+               if (snprintf (full_file, 1024, "%s/%s", host, file) >= 1024)
+                       return (-1);
+       }
+       else
+       {
+               if (snprintf (full_file, 1024, "%s", file) >= 1024)
+                       return (-1);
+       }
+
+       strncpy (full_file, file, 1024);
+
+       tmp = full_file + strlen (full_file) - 4;
+       assert (tmp > 0);
+
+       /* Change the filename for logfiles. */
+       if (strncmp (tmp, ".rrd", 4) == 0)
+       {
+               time_t now;
+               struct tm *tm;
+
+               now = time (NULL);
+               tm = localtime (&now);
+
+               strftime (tmp, 1024 - (tmp - full_file), "-%Y-%m-%d", tm);
+
+               /* `localtime(3)' returns a pointer to static data,
+                * therefore the pointer may not be free'd. */
+       }
+       else
+               DBG ("The filename ends with `%s' which is unexpected.", tmp);
+
+       if (stat (full_file, &statbuf) == -1)
+       {
+               if (errno == ENOENT)
+               {
+                       if (log_create_file (full_file, ds_def, ds_num))
+                               return (-1);
+               }
                else
                {
-                       syslog (LOG_ERR, "stat %s: %s", dir, strerror (errno));
+                       syslog (LOG_ERR, "stat %s: %s", full_file, strerror (errno));
                        return (-1);
                }
        }
-       else if (!S_ISDIR (statbuf.st_mode))
+       else if (!S_ISREG (statbuf.st_mode))
        {
-               syslog (LOG_ERR, "stat %s: Not a directory!", dir);
+               syslog (LOG_ERR, "stat %s: Not a regular file!", full_file);
                return (-1);
        }
 
+
+       fp = fopen (full_file, "a");
+       if (fp == NULL)
+       {
+               syslog (LOG_WARNING, "Failed to append to %s: %s", full_file,
+                               strerror(errno));
+               return (-1);
+       }
+       fprintf(fp, "%s\n", values);
+       fclose(fp);
+
        return (0);
-}
+} /* int log_update_file */
 
-int rrd_create_file (char *filename, char **ds_def, int ds_num)
+#if HAVE_LIBRRD
+static int rrd_create_file (char *filename, char **ds_def, int ds_num)
 {
        char **argv;
        int argc;
        int i, j;
        int status = 0;
 
+       if (check_create_dir (filename))
+               return (-1);
+
        argc = ds_num + rra_num + 4;
 
        if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
@@ -160,7 +451,7 @@ int rrd_create_file (char *filename, char **ds_def, int ds_num)
        }
 
        free (argv);
-       
+
        return (status);
 }
 #endif /* HAVE_LIBRRD */
@@ -168,17 +459,22 @@ int rrd_create_file (char *filename, char **ds_def, int ds_num)
 int rrd_update_file (char *host, char *file, char *values,
                char **ds_def, int ds_num)
 {
-#ifdef HAVE_LIBRRD
+#if HAVE_LIBRRD
        struct stat statbuf;
        char full_file[1024];
        char *argv[4] = { "update", full_file, values, NULL };
+#endif /* HAVE_LIBRRD */
+
+       /* I'd rather have a function `common_update_file' to make this
+        * decission, but for that we'd need to touch all plugins.. */
+       if (operating_mode == MODE_LOG)
+               return (log_update_file (host, file, values,
+                                       ds_def, ds_num));
 
+#if HAVE_LIBRRD
        /* host == NULL => local mode */
        if (host != NULL)
        {
-               if (check_create_dir (host))
-                       return (-1);
-
                if (snprintf (full_file, 1024, "%s/%s", host, file) >= 1024)
                        return (-1);
        }
@@ -214,9 +510,13 @@ int rrd_update_file (char *host, char *file, char *values,
                syslog (LOG_WARNING, "rrd_update failed: %s: %s", full_file, rrd_get_error ());
                return (-1);
        }
-#endif /* HAVE_LIBRRD */
-
        return (0);
+/* #endif HAVE_LIBRRD */
+
+#else
+       syslog (LOG_ERR, "`rrd_update_file' was called, but collectd isn't linked against librrd!");
+       return (-1);
+#endif
 }
 
 #ifdef HAVE_LIBKSTAT