]> git.itanic.dy.fi Git - rrdd/blobdiff - onewire_parser.c
onewire_parser.c: Fix compiler warnings about string lengths
[rrdd] / onewire_parser.c
index 9f4b404c480bdcc87ef2c88c37f3b6c9eb5441e9..958cff952cb7c8bbdfcca76f0f013ed0f6e92bd3 100644 (file)
@@ -1,20 +1,95 @@
 #include <stdio.h>
 #include <ownetapi.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
 
-#include "process.h"
 #include "parser.h"
 #include "debug.h"
 #include "string.h"
 #include "utils.h"
 #include "plugin.h"
 #include "version.h"
+#include "utils.h"
 
-static struct mutex server_lock = {
-       .name = "server_addr_lock",
-       .lock = PTHREAD_MUTEX_INITIALIZER,
+struct owparser_state {
+       double prev_delta[20];
+       double prev_data;
 };
 
-static int parse_opts(const char *str, char *ow_path, size_t pathlen, double *offset)
+static struct owparser_state *allocate_parser_state(const char **datastr)
+{
+       int i;
+
+       /*
+        * Count how many sensor entries we need. First entry belongs
+        * to server address or mount point and last one is NULL. So
+        * the index final is the count of actual valid sensor
+        * entries.
+        */
+       for (i = 0; datastr[i]; i++)
+               ;
+
+       return calloc(sizeof(struct owparser_state), i);
+}
+
+static double max_glitch_delta(const struct owparser_state *s)
+{
+       double max_delta = 0;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(s->prev_delta); i++)
+               max_delta = max(s->prev_delta[i], max_delta);
+
+       return max_delta;
+}
+
+static int might_be_glitch(double data, const struct owparser_state *s)
+{
+       double max_delta, delta;
+
+       /* The known bad data value from the sensor */
+       if (data == 85)
+               return 1;
+
+       max_delta = max_glitch_delta(s);
+
+       /* Probably no enough data yet, so no glitch detection */
+       if (max_delta == 0)
+               return 0;
+
+       /*
+        * Simple glitch detection. If delta to previous value is more
+        * than twice as larger as any of the older delta, we might
+        * have a glitch
+        */
+       delta = fabs(data - s->prev_data);
+
+       return delta > max_delta * 2;
+}
+
+static void update_glitch_data(double data, struct owparser_state *s)
+{
+       double max_delta = 0;
+       int i;
+
+       for (i = 1; i < ARRAY_SIZE(s->prev_delta); i++) {
+               s->prev_delta[i - 1] = s->prev_delta[i];
+               max_delta = max(s->prev_delta[i], max_delta);
+       }
+
+       /* Avoid storing the first incorrect delta value */
+       if (s->prev_data || max_delta)
+               s->prev_delta[--i] = fabs(data - s->prev_data);
+
+       s->prev_data = data;
+}
+
+static int parse_opts(const char *str, char *ow_path, size_t pathlen,
+               double *offset)
 {
        char *endptr;
        const char *start_str = str;
@@ -32,7 +107,7 @@ static int parse_opts(const char *str, char *ow_path, size_t pathlen, double *of
                        break;
 
        /* Copy the onewire path without options */
-       strncpy(ow_path, start_str, pathlen);
+       strncpy(ow_path, start_str, pathlen - 1);
        ow_path[str - start_str] = '\0';
 
        /* Get the next non-space, which is where the argument begins */
@@ -54,11 +129,15 @@ static int parse_opts(const char *str, char *ow_path, size_t pathlen, double *of
 
 static int make_uncached(char *path, size_t len)
 {
-       char p1[32], p2[32], *p = path;
+       int ret;
+       char p1[1028], p2[1028], *p = path;
 
        if (strstr(path, "/uncached/"))
                return 0;
 
+       p1[sizeof(p1) - 1] = '\0';
+       p2[sizeof(p2) - 1] = '\0';
+
        /*
         * Naively assume the "uncached" string can be put after the
         * first slash
@@ -72,64 +151,149 @@ static int make_uncached(char *path, size_t len)
        *p = 0;
        p++;
 
-       strncpy(p1, path, sizeof(p1));
-       strncpy(p2, p, sizeof(p2));
-       snprintf(path, len, "%s/uncached/%s", p1, p2);
+       strncpy(p1, path, sizeof(p1) - 1);
+       strncpy(p2, p, sizeof(p2) - 1);
+       ret = snprintf(path, len, "%s/uncached/%s", p1, p2);
+
+       /* No actual data overflow, snprintf just couldn't fit all data in the buffer */
+       if (ret >= RRD_DATA_MAX_LEN)
+               pr_err("Buffer overlfow\n");
 
        return 0;
 }
 
-static int onewire_parser(char *rrd_data, const char **parser_data)
+static int owfs_read(const char *mount_point, const char *path, char **res)
 {
-       static OWNET_HANDLE handle;
-       static char *server_addr;
-       char buf[24], *tmp;
-       int i = 1, ret;
-       int max_str = RRD_DATA_MAX_LEN;
+       char result[64];
+       char file[2048];
+       int fd, ret;
 
-       if (!parser_data) {
-               pr_err("No parser data available\n");
+       snprintf(file, sizeof(file), "%s/%s", mount_point, path);
+
+       fd = open(file, O_RDONLY | O_CLOEXEC);
+       if (fd < 0) {
+               pr_err("Failed to open file %s: %m\n", file);
                return -1;
        }
 
-       if (!parser_data[0]) {
-               pr_err("Server address not specified\n");
-               return -1;
+       ret = read(fd, result, sizeof(result));
+       if (ret < 0) {
+               pr_err("Failed to read from file %s: %m\n", file);
+               goto out_close;
+       }
+
+       *res = strndup(result, sizeof(result));
+
+out_close:
+       close(fd);
+
+       return ret;
+}
+
+static void enable_simultaneous_reading(const char *mountpoint)
+{
+       static time_t last_simultaneous;
+       static struct mutex lock = {
+               .lock = PTHREAD_MUTEX_INITIALIZER,
+       };
+
+       time_t now = time(0);
+       int fd;
+       int ret;
+       char path[4096];
+       char one = '1';
+
+       mutex_lock(&lock);
+       /* Arbitrary 10 second limit between simultaneous reads */
+       if (now < last_simultaneous + 10) {
+               mutex_unlock(&lock);
+               return;
        }
 
+       last_simultaneous = now;
+       mutex_unlock(&lock);
+
        /*
-        * No point trying to connect to the server more than
-        * once. Also we don't know how thread safe libownet is. One
-        * lock to protect it all.
+        * We only protect setting the variable. From now on we have
+        * 10 seconds time until we could race writing multiple times
+        * to this file. If that happens, well, can't help it..
         */
-       mutex_lock(&server_lock);
+
+       strncpy(path, mountpoint, sizeof(path) - 1);
+       strncat(path, "/simultaneous/temperature", sizeof(path) - 1);
+       path[sizeof(path) - 1 ] = '\0';
+
+       fd = open(path, O_WRONLY);
+       if (path < 0) {
+               pr_err("Failed to open %s for writing: %m\n", path);
+               return;
+       }
+
+       ret = write(fd, &one, 1);
+       if (ret < 0)
+               pr_warn("Failed to write to %s: %m\n", path);
+
+       close(fd);
+}
+
+static int is_mount_point(const char *str)
+{
        /*
-        * Keep one server connection alive at all times. This
-        * prevents file descriptor leak with older libownet.
+        * Filesystem paths begin with a slash, everything else must
+        * be a network addresses
         */
-       if (!server_addr || handle < 0) {
-               if (server_addr)
-                       free(server_addr);
-               server_addr = strdup(parser_data[0]);
-               handle = OWNET_init(server_addr);
-       } else if (strcmp(server_addr, parser_data[0])) {
-               OWNET_close(handle);
-               free(server_addr);
-               server_addr = strdup(parser_data[0]);
-               handle = OWNET_init(server_addr);
-       }
+       if (str[0] == '/')
+               return 1;
+
+       return 0;
+}
+
+static int onewire_parser(char *rrd_data, const char **parser_data, void **s)
+{
+       OWNET_HANDLE h;
+       const char *server_addr, *mount_point;
+       struct owparser_state *state = *s;
+       char buf[24], *tmp;
+       int i = 1, ret;
+       int max_str = RRD_DATA_MAX_LEN;
+       int is_mountpoint = is_mount_point(parser_data[0]);
 
-       if (handle < 0) {
-               pr_err("Failed to connect to server %s\n", server_addr);
-               mutex_unlock(&server_lock);
+       if (!parser_data) {
+               pr_err("No parser data available\n");
                return -1;
        }
 
+       if (!state)
+               *s = state = allocate_parser_state(parser_data);
+
+       if (is_mountpoint) {
+               mount_point = parser_data[0];
+
+               if (!mount_point) {
+                       pr_err("Server address not specified\n");
+                       return -1;
+               }
+       } else {
+               server_addr = parser_data[0];
+
+               if (!server_addr) {
+                       pr_err("Server address not specified\n");
+                       return -1;
+               }
+
+               h = OWNET_init(server_addr);
+               if (h < 0) {
+                       pr_err("Failed to connect to server %s\n", server_addr);
+                       return -1;
+               }
+       }
+
        while (parser_data[i]) {
-               double offset = 0, data;
+               double offset = 0, data, prev_data = 85;
                char *endptr;
                char ow_path[1024];
                int retries = 0;
+               int glitches = 0;
 
                if (!strcmp("U", parser_data[i])) {
 undefined:
@@ -141,49 +305,83 @@ undefined:
 
                parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset);
 
+               if (is_mountpoint)
+                       enable_simultaneous_reading(mount_point);
+
                while (1) {
-                       int fail, j;
+                       int j;
                        char *tmp2;
 
+                       tmp = NULL;
                        pr_info("Reading data for entry %s with offset of %.2f\n",
                                ow_path, offset);
-                       ret = OWNET_read(handle, ow_path, &tmp);
-                       if (ret < 0)
-                               goto err;
+
+                       if (is_mountpoint)
+                               ret = owfs_read(mount_point, ow_path, &tmp);
+                       else
+                               ret = OWNET_read(h, ow_path, &tmp);
+
                        /* Skip leading white space */
                        tmp2 = tmp;
                        for (j = 0; j < ret && *tmp2 == ' '; j++)
                                tmp2++;
 
-                       fail = !strncmp(tmp2, "85", 2);
+                       if (ret <= 0)
+                               goto retry;
+
+                       /*
+                        * Older versions of OWNET_read did not NULL
+                        * terminate data.
+                        */
+                       memcpy(buf, tmp, min(ret, sizeof(buf) -1));
+                       buf[ret] = 0;
+
+                       data = strtod(buf, &endptr);
 
+                       free(tmp);
+                       tmp = NULL;
+
+                       /*
+                        * If we read the almost same value as
+                        * previously, it's not a glitch
+                        */
+                       if (glitches && prev_data != 85) {
+                               double d = max_glitch_delta(&state[i]);
+
+                               if (fabs(data - prev_data) <= d * 2)
+                                       break;
+                       }
+
+                       if (might_be_glitch(data, &state[i]) &&
+                               glitches < 4 && retries < 7) {
+                               glitches++;
+                               prev_data = data;
+                               pr_info("Retrying due to a glitch: %f\n", data);
+                               goto retry;
+                       }
+
+                       break;
+retry:
                        /*
                         * In case of failure, retry with uncached
                         * data. This is likely to help as it forces a
                         * retry even if the sensor is missing from
-                        * the cache. We thread "85" also as a failure
-                        * above, as temp sensors some times report 85
-                        * under faulty conditions.
+                        * the cache. We treat "85" also as a failure,
+                        * as temp sensors some times report 85 under
+                        * faulty conditions.
                         */
-                       if (ret > 0 && !fail)
-                               break;
-
                        ret = make_uncached(ow_path, sizeof(ow_path));
-err:
                        if (retries >= 10 || ret < 0) {
                                pr_err("Failed to read entry %s: %m\n",
                                        parser_data[i]);
                                goto undefined;
                        }
                        retries++;
+                       if (tmp)
+                               free(tmp);
                }
 
-               /* The data from OWNET_read appears to not be NULL terminated */
-               memcpy(buf, tmp, MIN(ret, sizeof(buf) -1));
-               free(tmp);
-               buf[ret] = 0;
-
-               data = strtod(buf, &endptr);
+               update_glitch_data(data, &state[i]);
 
                if (endptr == buf) {
                        pr_err("Failed to parse data %s\n", buf);
@@ -207,7 +405,9 @@ next:
        }
        rrd_data = 0;
 
-       mutex_unlock(&server_lock);
+       if (!is_mountpoint)
+               OWNET_finish();
+
        return 0;
 }