]> git.itanic.dy.fi Git - rrdd/blob - onewire_parser.c
onewire_parser.c: Fix compiler warnings about string lengths
[rrdd] / onewire_parser.c
1 #include <stdio.h>
2 #include <ownetapi.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <math.h>
8 #include <time.h>
9
10 #include "parser.h"
11 #include "debug.h"
12 #include "string.h"
13 #include "utils.h"
14 #include "plugin.h"
15 #include "version.h"
16 #include "utils.h"
17
18 struct owparser_state {
19         double prev_delta[20];
20         double prev_data;
21 };
22
23 static struct owparser_state *allocate_parser_state(const char **datastr)
24 {
25         int i;
26
27         /*
28          * Count how many sensor entries we need. First entry belongs
29          * to server address or mount point and last one is NULL. So
30          * the index final is the count of actual valid sensor
31          * entries.
32          */
33         for (i = 0; datastr[i]; i++)
34                 ;
35
36         return calloc(sizeof(struct owparser_state), i);
37 }
38
39 static double max_glitch_delta(const struct owparser_state *s)
40 {
41         double max_delta = 0;
42         int i;
43
44         for (i = 0; i < ARRAY_SIZE(s->prev_delta); i++)
45                 max_delta = max(s->prev_delta[i], max_delta);
46
47         return max_delta;
48 }
49
50 static int might_be_glitch(double data, const struct owparser_state *s)
51 {
52         double max_delta, delta;
53
54         /* The known bad data value from the sensor */
55         if (data == 85)
56                 return 1;
57
58         max_delta = max_glitch_delta(s);
59
60         /* Probably no enough data yet, so no glitch detection */
61         if (max_delta == 0)
62                 return 0;
63
64         /*
65          * Simple glitch detection. If delta to previous value is more
66          * than twice as larger as any of the older delta, we might
67          * have a glitch
68          */
69         delta = fabs(data - s->prev_data);
70
71         return delta > max_delta * 2;
72 }
73
74 static void update_glitch_data(double data, struct owparser_state *s)
75 {
76         double max_delta = 0;
77         int i;
78
79         for (i = 1; i < ARRAY_SIZE(s->prev_delta); i++) {
80                 s->prev_delta[i - 1] = s->prev_delta[i];
81                 max_delta = max(s->prev_delta[i], max_delta);
82         }
83
84         /* Avoid storing the first incorrect delta value */
85         if (s->prev_data || max_delta)
86                 s->prev_delta[--i] = fabs(data - s->prev_data);
87
88         s->prev_data = data;
89 }
90
91 static int parse_opts(const char *str, char *ow_path, size_t pathlen,
92                 double *offset)
93 {
94         char *endptr;
95         const char *start_str = str;
96         const char offset_str[] = "offset=";
97
98         if (!offset)
99                 return 0;
100
101         /*
102          * Skip the onewire path entry. Options begin after the first
103          * white space
104          */
105         for (; *str; str++)
106                 if (isspace(*str))
107                         break;
108
109         /* Copy the onewire path without options */
110         strncpy(ow_path, start_str, pathlen);
111         ow_path[str - start_str] = '\0';
112
113         /* Get the next non-space, which is where the argument begins */
114         for (; *str; str++)
115                 if (!isspace(*str))
116                         break;
117
118         if (strncmp(str, offset_str, sizeof(offset_str) - 1))
119                 return 0;
120         str += sizeof(offset_str) - 1;
121
122         *offset = strtod(str, &endptr);
123
124         if (str != endptr)
125                 return 1;
126
127         return 0;
128 }
129
130 static int make_uncached(char *path, size_t len)
131 {
132         int ret;
133         char p1[1024], p2[1024], *p = path;
134
135         if (strstr(path, "/uncached/"))
136                 return 0;
137
138         p1[sizeof(p1) - 1] = '\0';
139         p2[sizeof(p2) - 1] = '\0';
140
141         /*
142          * Naively assume the "uncached" string can be put after the
143          * first slash
144          */
145         while (*p && *p != '/')
146                 p++;
147
148         if (!*p)
149                 return -1;
150
151         *p = 0;
152         p++;
153
154         strncpy(p1, path, sizeof(p1) - 1);
155         strncpy(p2, p, sizeof(p2) - 1);
156         ret = snprintf(path, len, "%s/uncached/%s", p1, p2);
157
158         /* No actual data overflow, snprintf just couldn't fit all data in the buffer */
159         if (ret >= RRD_DATA_MAX_LEN)
160                 pr_err("Buffer overlfow\n");
161
162         return 0;
163 }
164
165 static int owfs_read(const char *mount_point, const char *path, char **res)
166 {
167         char result[64];
168         char file[2048];
169         int fd, ret;
170
171         snprintf(file, sizeof(file), "%s/%s", mount_point, path);
172
173         fd = open(file, O_RDONLY | O_CLOEXEC);
174         if (fd < 0) {
175                 pr_err("Failed to open file %s: %m\n", file);
176                 return -1;
177         }
178
179         ret = read(fd, result, sizeof(result));
180         if (ret < 0) {
181                 pr_err("Failed to read from file %s: %m\n", file);
182                 goto out_close;
183         }
184
185         *res = strndup(result, sizeof(result));
186
187 out_close:
188         close(fd);
189
190         return ret;
191 }
192
193 static void enable_simultaneous_reading(const char *mountpoint)
194 {
195         static time_t last_simultaneous;
196         static struct mutex lock = {
197                 .lock = PTHREAD_MUTEX_INITIALIZER,
198         };
199
200         time_t now = time(0);
201         int fd;
202         int ret;
203         char path[4096];
204         char one = '1';
205
206         mutex_lock(&lock);
207         /* Arbitrary 10 second limit between simultaneous reads */
208         if (now < last_simultaneous + 10) {
209                 mutex_unlock(&lock);
210                 return;
211         }
212
213         last_simultaneous = now;
214         mutex_unlock(&lock);
215
216         /*
217          * We only protect setting the variable. From now on we have
218          * 10 seconds time until we could race writing multiple times
219          * to this file. If that happens, well, can't help it..
220          */
221
222         strncpy(path, mountpoint, sizeof(path) - 1);
223         strncat(path, "/simultaneous/temperature", sizeof(path) - 1);
224         path[sizeof(path) - 1 ] = '\0';
225
226         fd = open(path, O_WRONLY);
227         if (path < 0) {
228                 pr_err("Failed to open %s for writing: %m\n", path);
229                 return;
230         }
231
232         ret = write(fd, &one, 1);
233         if (ret < 0)
234                 pr_warn("Failed to write to %s: %m\n", path);
235
236         close(fd);
237 }
238
239 static int is_mount_point(const char *str)
240 {
241         /*
242          * Filesystem paths begin with a slash, everything else must
243          * be a network addresses
244          */
245         if (str[0] == '/')
246                 return 1;
247
248         return 0;
249 }
250
251 static int onewire_parser(char *rrd_data, const char **parser_data, void **s)
252 {
253         OWNET_HANDLE h;
254         const char *server_addr, *mount_point;
255         struct owparser_state *state = *s;
256         char buf[24], *tmp;
257         int i = 1, ret;
258         int max_str = RRD_DATA_MAX_LEN;
259         int is_mountpoint = is_mount_point(parser_data[0]);
260
261         if (!parser_data) {
262                 pr_err("No parser data available\n");
263                 return -1;
264         }
265
266         if (!state)
267                 *s = state = allocate_parser_state(parser_data);
268
269         if (is_mountpoint) {
270                 mount_point = parser_data[0];
271
272                 if (!mount_point) {
273                         pr_err("Server address not specified\n");
274                         return -1;
275                 }
276         } else {
277                 server_addr = parser_data[0];
278
279                 if (!server_addr) {
280                         pr_err("Server address not specified\n");
281                         return -1;
282                 }
283
284                 h = OWNET_init(server_addr);
285                 if (h < 0) {
286                         pr_err("Failed to connect to server %s\n", server_addr);
287                         return -1;
288                 }
289         }
290
291         while (parser_data[i]) {
292                 double offset = 0, data, prev_data = 85;
293                 char *endptr;
294                 char ow_path[1024];
295                 int retries = 0;
296                 int glitches = 0;
297
298                 if (!strcmp("U", parser_data[i])) {
299 undefined:
300                         ret = snprintf(rrd_data, max_str, "U");
301                         max_str -= ret;
302                         rrd_data += ret;
303                         goto next;
304                 }
305
306                 parse_opts(parser_data[i], ow_path, sizeof(ow_path), &offset);
307
308                 if (is_mountpoint)
309                         enable_simultaneous_reading(mount_point);
310
311                 while (1) {
312                         int j;
313                         char *tmp2;
314
315                         tmp = NULL;
316                         pr_info("Reading data for entry %s with offset of %.2f\n",
317                                 ow_path, offset);
318
319                         if (is_mountpoint)
320                                 ret = owfs_read(mount_point, ow_path, &tmp);
321                         else
322                                 ret = OWNET_read(h, ow_path, &tmp);
323
324                         /* Skip leading white space */
325                         tmp2 = tmp;
326                         for (j = 0; j < ret && *tmp2 == ' '; j++)
327                                 tmp2++;
328
329                         if (ret <= 0)
330                                 goto retry;
331
332                         /*
333                          * Older versions of OWNET_read did not NULL
334                          * terminate data.
335                          */
336                         memcpy(buf, tmp, min(ret, sizeof(buf) -1));
337                         buf[ret] = 0;
338
339                         data = strtod(buf, &endptr);
340
341                         free(tmp);
342                         tmp = NULL;
343
344                         /*
345                          * If we read the almost same value as
346                          * previously, it's not a glitch
347                          */
348                         if (glitches && prev_data != 85) {
349                                 double d = max_glitch_delta(&state[i]);
350
351                                 if (fabs(data - prev_data) <= d * 2)
352                                         break;
353                         }
354
355                         if (might_be_glitch(data, &state[i]) &&
356                                 glitches < 4 && retries < 7) {
357                                 glitches++;
358                                 prev_data = data;
359                                 pr_info("Retrying due to a glitch: %f\n", data);
360                                 goto retry;
361                         }
362
363                         break;
364 retry:
365                         /*
366                          * In case of failure, retry with uncached
367                          * data. This is likely to help as it forces a
368                          * retry even if the sensor is missing from
369                          * the cache. We treat "85" also as a failure,
370                          * as temp sensors some times report 85 under
371                          * faulty conditions.
372                          */
373                         ret = make_uncached(ow_path, sizeof(ow_path));
374                         if (retries >= 10 || ret < 0) {
375                                 pr_err("Failed to read entry %s: %m\n",
376                                         parser_data[i]);
377                                 goto undefined;
378                         }
379                         retries++;
380                         if (tmp)
381                                 free(tmp);
382                 }
383
384                 update_glitch_data(data, &state[i]);
385
386                 if (endptr == buf) {
387                         pr_err("Failed to parse data %s\n", buf);
388                         goto undefined;
389                 }
390
391                 data += offset;
392
393                 ret = snprintf(rrd_data, max_str, "%f", data);
394                 max_str -= ret;
395                 rrd_data += ret;
396
397 next:
398                 i++;
399                 if (!parser_data[i])
400                         break;
401
402                 ret = snprintf(rrd_data, max_str, ":");
403                 max_str -= ret;
404                 rrd_data += ret;
405         }
406         rrd_data = 0;
407
408         if (!is_mountpoint)
409                 OWNET_finish();
410
411         return 0;
412 }
413
414 static struct parser_info onewire_parser_info = {
415         .name = "onewire",
416         .parse = onewire_parser,
417 };
418
419 static int init_onewire_parser(void)
420 {
421         return register_parser(&onewire_parser_info);
422 }
423
424 struct plugin_info plugin_info = {
425         .name = "onewire_parser",
426         .init = init_onewire_parser,
427         .version = RRDD_VERSION,
428 };