]> git.itanic.dy.fi Git - rrdd/blob - rrdtool.c
rrdtool: Fix use of uninitialized buffers in string operations
[rrdd] / rrdtool.c
1 #include <time.h>
2
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <unistd.h>
7 #include <string.h>
8
9 #include "rrdtool.h"
10 #include "process.h"
11 #include "parser.h"
12 #include "debug.h"
13 #include "string.h"
14
15 #define MAX_ARGS        512
16 #define ARGSTR_LEN      32768
17
18 #define RRDTOOL_CMD "/usr/bin/rrdtool"
19
20 /*
21  * Add new argument to a argument list
22  *
23  * args         pointer list to arguments
24  * argcnt       argument counter
25  * argstr       array where the actual arguments are stored
26  * idx          index in the argstr where the new argument will be appended
27  */
28 #define add_arg(args, argcnt, argstr, idx, fmt, arg...) \
29         args[argcnt] = argstr + idx;                    \
30         idx += sprintf(argstr + idx, fmt, ##arg);       \
31         argcnt++;                                       \
32         args[argcnt] = 0;                               \
33         argstr[++idx] = 0
34
35 int rrdtool_draw_image(struct rrd_image *image)
36 {
37         char cmd[] = RRDTOOL_CMD;
38 //      char cmd[] = "echo";
39         char *args[512], argstr[ARGSTR_LEN];
40         int idx = 0, argcnt = 0, i,j;
41         char timestamp[256];
42         char tmp[sizeof(timestamp)];
43         char tmpfile[256];
44         time_t t = time(0);
45         const char *updatestr = "Last update %d.%m.%Y %T (%Z)";
46
47         pr_info("Drawing image %s\n", image->image_filename);
48
49         tmpfile[0] = 0;
50         tmp[0] = 0;
51         strncat(tmpfile, image->image_filename, sizeof(tmpfile) - 1);
52         strncat(tmpfile, ".tmp", sizeof(tmpfile) - 1);
53
54         if (image->updatestr)
55                 updatestr = image->updatestr;
56
57         strftime(tmp, sizeof(tmp), updatestr, localtime(&t));
58         for (i = 0, j = 0; j < sizeof(tmp);) {
59                 if (tmp[i] == ':') {
60                         timestamp[j++] = '\\';
61                 }
62                 timestamp[j++] = tmp[i++];
63                 if (!tmp[i])
64                         break;
65         }
66         timestamp[j] = 0;
67
68
69         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
70         add_arg(args, argcnt, argstr, idx, "graph");
71         add_arg(args, argcnt, argstr, idx, "%s", tmpfile);
72
73         add_arg(args, argcnt, argstr, idx, "--start");
74         add_arg(args, argcnt, argstr, idx, "%s", image->timestart);
75         add_arg(args, argcnt, argstr, idx, "--end");
76         add_arg(args, argcnt, argstr, idx, "%s", image->timeend);
77         add_arg(args, argcnt, argstr, idx, "--width");
78         add_arg(args, argcnt, argstr, idx, "%d", image->width);
79         add_arg(args, argcnt, argstr, idx, "--height");
80         add_arg(args, argcnt, argstr, idx, "%d", image->height);
81         add_arg(args, argcnt, argstr, idx, "--imgformat");
82         add_arg(args, argcnt, argstr, idx, "%s", image->imageformat);
83
84         for (i = 0; image->options[i]; i++) {
85                 add_arg(args, argcnt, argstr, idx, "%s", image->options[i]);
86         }
87
88         for (i = 0; image->text[i]; i++) {
89                 args[argcnt++] = (char *)image->text[i];
90         }
91
92         add_arg(args, argcnt, argstr, idx, "COMMENT: %s\\c", timestamp);
93
94         run(cmd, args);
95
96         rename(tmpfile, image->image_filename);
97
98         return 0;
99 }
100
101 int rrdtool_draw_images(struct rrd_image **image)
102 {
103         int i;
104         for (i = 0; image[i]; i++)
105                 queue_work(WORK_PRIORITY_LOW, "rrdtool_draw_image",
106                         (work_fn_t *)rrdtool_draw_image, image[i]);
107
108         return 0;
109 }
110
111 static int sanitize_rrd_update_data(char *data)
112 {
113         char clean_data[RRD_DATA_MAX_LEN];
114         int entries = 0;
115         int minus;
116         char *src, *end, *cln;
117
118         data[RRD_DATA_MAX_LEN - 1] = 0;
119         src = data;
120         cln = clean_data;
121
122         /*
123          * Copy a legit floating point number to clean_data buffer
124          * starting from *src and ending to next ':'. If no legit
125          * number could be found, put a 'U' there instead to make
126          * rrdtool to understand this datapoint is undefined.
127          */
128
129         while (src < data + RRD_DATA_MAX_LEN && *src) {
130                 minus = 0;
131
132                 /* skip any non_numbers but not ':' */
133                 while (*src && !isdigit(*src) && *src != '-' && *src != ':')
134                         src++;
135
136                 if (*src == '-') {
137                         src++;
138                         minus = 1;
139                 }
140
141                 /* Now find the end of the number */
142                 end = skip_numbers(src);
143
144                 /* Floating point numberrs may have a dot with more numbers */
145                 if (*end == '.') {
146                         end++;
147                         end = skip_numbers(end);
148                 }
149
150                 /*
151                  * Now we have gone past the number, there should be a
152                  * colon or zero byte. If src == end, there was no
153                  * number and the entry is undefined instead.
154                  */
155                 if ((*end == ':' || !*end) && src != end) {
156                         if (minus) {
157                                 *cln = '-';
158                                 cln++;
159                         }
160
161                         /*
162                          * Copy the legit number and start copying the
163                          * next one
164                          */
165                         for (; src <= end; src++, cln++)
166                                 *cln = *src;
167
168                         goto next;
169                 }
170
171                 /* Skip over whatever junk there might be */
172                 while (*end != ':' && *end)
173                         end++;
174
175                 /* Mark the entry as undefined */
176                 *cln = 'U';
177                 cln++;
178                 *cln = ':';
179                 cln++;
180         next:
181                 end++;
182                 src = end;
183                 entries++;
184         }
185
186         /*
187          * If last entry was undefined, we need to remove the extra
188          * colon at the end
189          */
190         if (*(cln - 1) == ':')
191                 cln--;
192         *cln = '\0';
193
194         strncpy(data, clean_data, RRD_DATA_MAX_LEN);
195         return entries;
196 }
197
198 static int write_to_logfile(struct rrd_database *rrd, const char *data)
199 {
200         time_t t = time(NULL);
201         int fd, ret;
202         int spacing, i;
203         char filename[1024];
204         char logstr[RRD_DATA_MAX_LEN * 2] = { 0 };
205         const char *time_stamp_fmt = "%Y.%m.%d %H:%M ";
206         char *str_ptr;
207
208         if (!rrd->logfile)
209                 return 0;
210
211         if (rrd->logfile_timestamp_fmt)
212                 time_stamp_fmt = rrd->logfile_timestamp_fmt;
213
214         strftime(filename, sizeof(filename), rrd->logfile, localtime(&t));
215
216         fd = open(filename, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0644);
217         if (fd < 0) {
218                 pr_err("Failed to open file %s for logging: %m\n", filename);
219                 return -1;
220         }
221
222         strftime(logstr, sizeof(logstr), time_stamp_fmt, localtime(&t));
223
224         str_ptr = logstr + strlen(logstr);
225
226         /* Skip the "N: part */
227         while (*data != ':')
228                 data++;
229         data++;
230
231         spacing = 12;
232
233         while (*data && str_ptr - logstr < sizeof(logstr) - 1) {
234                 if (*data == ':') {
235                         *str_ptr++ = ' ';
236                         for (i = 0; i < spacing; i++)
237                                 *str_ptr++ = ' ';
238                         spacing = 12;
239                         data++;
240                         continue;
241                 }
242
243                 *str_ptr++ = *data++;
244                 spacing--;
245         }
246         *str_ptr++ = '\n';
247         *str_ptr++ = 0;
248
249         ret = write(fd, logstr, strlen(logstr));
250         if (ret < 0)
251                 pr_err("Failed to write to logfile %s: %m\n", filename);
252
253         close(fd);
254
255         return ret < 0 ? ret : 0;
256 }
257
258 static int run_post_draw_cmd(struct rrd_database *rrd)
259 {
260         pr_info("Running post draw command for %s\n", rrd->name);
261
262         if (rrd->post_draw_cmd && !strcmp(rrd->post_draw_cmd[0], "shell"))
263                 run(rrd->post_draw_cmd[1], &rrd->post_draw_cmd[1]);
264
265         return 0;
266 }
267
268 static int do_rrdtool_update_data(struct rrd_database *rrd)
269 {
270         char data[RRD_DATA_MAX_LEN + 12]; /* 12 == "%s:" + NULL termination */
271         char cmd[] = RRDTOOL_CMD;
272 //      char cmd[] = "echo";
273         char *const cmdline[] = {
274                 RRDTOOL_CMD,
275                 "update",
276                 (char *const)rrd->filename,
277                 data,
278                 0
279         };
280         int l;
281
282         bzero(data, sizeof(data));
283         l = sprintf(data, "%zd:", time(NULL));
284
285         if (rrd->parser && rrd->parser->parse) {
286                 rrd->parser->parse(data + l, rrd->parser_data,
287                                 &rrd->parser_state);
288                 data[RRD_DATA_MAX_LEN + 2] = '\0';
289
290                 pr_info("Data: %s\n", data);
291
292                 sanitize_rrd_update_data(data + l);
293                 write_to_logfile(rrd, data);
294
295                 run(cmd, cmdline);
296         }
297
298         if (rrd->pre_draw_cmd && !strcmp(rrd->pre_draw_cmd[0], "shell")) {
299                 run(rrd->pre_draw_cmd[1], &rrd->pre_draw_cmd[1]);
300         }
301
302         if (rrd->images)
303                 rrdtool_draw_images(rrd->images);
304
305         /*
306          * We rely on the fact that rrdtool_draw_images queues image
307          * drawings into low priority queue and the post draw queue is
308          * placed on the queue after images. This ensures post draw
309          * command is not started before images are started.
310          *
311          * There is nothing that guarantees post_draw_cmd is executed
312          * after all images are completed though, but it's close..
313          */
314         if (rrd->post_draw_cmd)
315                 queue_work(WORK_PRIORITY_LOW, "rrdtool_post_draw_cmd",
316                         (work_fn_t *)run_post_draw_cmd, rrd);
317
318         return 0;
319 }
320
321 int rrdtool_update_data(struct rrd_database *rrd)
322 {
323         time_t now = time(0);
324
325         /*
326          * This will put our last update slightly into past, but
327          * ensures our update interval will not drift over time.
328          */
329         rrd->last_update = now - now % rrd->interval;
330
331         return queue_work(WORK_PRIORITY_HIGH, "rrdtool_update_data",
332                         (work_fn_t *)do_rrdtool_update_data, rrd);
333 }
334
335 /*
336  * Walk through the database list and return the first database which
337  * last update is too far in past
338  */
339 struct rrd_database *get_outdated_db(struct rrd_database **dblist)
340 {
341         int i;
342         time_t now = time(0);
343
344         for (i = 0; dblist[i]; i++) {
345                 if ((dblist[i]->last_update + dblist[i]->interval) - now <= 0)
346                         return dblist[i];
347         }
348
349         /* Nothing to update this time, return null */
350         return NULL;
351 }
352
353 /*
354  * See how long we may sleep until it is required to run an update
355  * again
356  */
357 int get_next_update(struct rrd_database **dblist, const char **name)
358 {
359         int i, sleeptime = 0, diff;
360         time_t now = time(0);
361
362         for (i = 0; dblist[i]; i++) {
363                 diff = dblist[i]->last_update + dblist[i]->interval - now;
364                 if (!sleeptime) {
365                         sleeptime = diff;
366                         *name = dblist[i]->name;
367                 }
368                 if (sleeptime > diff) {
369                         sleeptime = diff;
370                         *name = dblist[i]->name;
371                 }
372                 if (sleeptime <= 0)
373                         return 0;
374         }
375
376         return sleeptime;
377 }
378
379 static int database_exists(struct rrd_database *db)
380 {
381         struct stat s;
382
383         /* If the filename exists, stat will return zero */
384         if (db->filename)
385                 return !stat(db->filename, &s);
386
387         return 0;
388 }
389
390 static int create_database(struct rrd_database *db)
391 {
392         char cmd[] = RRDTOOL_CMD;
393 //      char cmd[] = "echo";
394         char *args[512], argstr[ARGSTR_LEN];
395         int idx = 0, argcnt = 0;
396         int i;
397
398         if (!db->filename) {
399                 pr_err("Database %s missing database filename\n", db->name);
400                 return -1;
401         }
402
403         if (!db->sources || !db->archives) {
404                 pr_err("Cannot create db \"%s\", insufficient source data\n",
405                         db->filename);
406                 return -1;
407         }
408
409         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
410         add_arg(args, argcnt, argstr, idx, "create");
411         add_arg(args, argcnt, argstr, idx, "%s", db->filename);
412         add_arg(args, argcnt, argstr, idx, "--step");
413         add_arg(args, argcnt, argstr, idx, "%d", db->interval);
414
415         for (i = 0; db->sources[i].type; i++) {
416                 add_arg(args, argcnt, argstr, idx, "DS:%s:%s:%d:%f:%f",
417                         db->sources[i].name,
418                         db->sources[i].type,
419                         db->sources[i].heartbeat,
420                         db->sources[i].min,
421                         db->sources[i].max);
422         }
423
424         for (i = 0; db->archives[i].type; i++) {
425                 add_arg(args, argcnt, argstr, idx, "RRA:%s:%f:%d:%d",
426                         db->archives[i].type,
427                         db->archives[i].xff,
428                         db->archives[i].steps,
429                         db->archives[i].rows);
430         }
431
432         run(cmd, args);
433
434         return 0;
435 }
436
437 int rrdtool_create_missing_databases(struct rrd_database *dbs[])
438 {
439         struct rrd_database *db;
440         int i, ret = 0;
441
442         for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
443                 if (database_exists(db)) {
444                         pr_info("database %s found\n", db->filename);
445                         continue;
446                 }
447                 pr_info("Database %s missing, creating\n", db->filename);
448                 ret |= create_database(db);
449         }
450
451         return ret;
452 }