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