]> git.itanic.dy.fi Git - rrdd/blob - rrdtool.c
Replace strncat with _strlcat
[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 #include "utils.h"
15
16 #define MAX_ARGS        512
17 #define ARGSTR_LEN      32768
18
19 #define RRDTOOL_CMD "/usr/bin/rrdtool"
20
21 /*
22  * Add new argument to a argument list
23  *
24  * args         pointer list to arguments
25  * argcnt       argument counter
26  * argstr       array where the actual arguments are stored
27  * idx          index in the argstr where the new argument will be appended
28  */
29 #define add_arg(args, argcnt, argstr, idx, fmt, arg...) \
30         args[argcnt] = argstr + idx;                    \
31         idx += sprintf(argstr + idx, fmt, ##arg);       \
32         argcnt++;                                       \
33         args[argcnt] = 0;                               \
34         argstr[++idx] = 0
35
36 int rrdtool_draw_image(struct rrd_image *image)
37 {
38         char cmd[] = RRDTOOL_CMD;
39 //      char cmd[] = "echo";
40         char *args[512], argstr[ARGSTR_LEN];
41         int idx = 0, argcnt = 0, i,j;
42         char timestamp[256];
43         char tmp[sizeof(timestamp)];
44         char tmpfile[256];
45         time_t t = time(0);
46         const char *updatestr = "Last update %d.%m.%Y %T (%Z)";
47
48         pr_info("Drawing image %s\n", image->image_filename);
49
50         tmpfile[0] = 0;
51         tmp[0] = 0;
52         strncpy(tmpfile, image->image_filename, sizeof(tmpfile) - 1);
53         _strlcat(tmpfile, ".tmp", sizeof(tmpfile));
54
55         if (image->updatestr)
56                 updatestr = image->updatestr;
57
58         strftime(tmp, sizeof(tmp), updatestr, localtime(&t));
59         for (i = 0, j = 0; j < sizeof(tmp);) {
60                 if (tmp[i] == ':') {
61                         timestamp[j++] = '\\';
62                 }
63                 timestamp[j++] = tmp[i++];
64                 if (!tmp[i])
65                         break;
66         }
67         timestamp[j] = 0;
68
69
70         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
71         add_arg(args, argcnt, argstr, idx, "graph");
72         add_arg(args, argcnt, argstr, idx, "%s", tmpfile);
73
74         add_arg(args, argcnt, argstr, idx, "--start");
75         add_arg(args, argcnt, argstr, idx, "%s", image->timestart);
76         add_arg(args, argcnt, argstr, idx, "--end");
77         add_arg(args, argcnt, argstr, idx, "%s", image->timeend);
78         add_arg(args, argcnt, argstr, idx, "--width");
79         add_arg(args, argcnt, argstr, idx, "%d", image->width);
80         add_arg(args, argcnt, argstr, idx, "--height");
81         add_arg(args, argcnt, argstr, idx, "%d", image->height);
82         add_arg(args, argcnt, argstr, idx, "--imgformat");
83         add_arg(args, argcnt, argstr, idx, "%s", image->imageformat);
84
85         for (i = 0; image->options[i]; i++) {
86                 add_arg(args, argcnt, argstr, idx, "%s", image->options[i]);
87         }
88
89         for (i = 0; image->text[i]; i++) {
90                 args[argcnt++] = (char *)image->text[i];
91         }
92
93         add_arg(args, argcnt, argstr, idx, "COMMENT: %s\\c", timestamp);
94
95         run(cmd, args);
96
97         rename(tmpfile, image->image_filename);
98
99         return 0;
100 }
101
102 int rrdtool_draw_images(struct rrd_image **image)
103 {
104         int i;
105         for (i = 0; image[i]; i++)
106                 queue_work(WORK_PRIORITY_LOW, "rrdtool_draw_image",
107                         (work_fn_t *)rrdtool_draw_image, image[i]);
108
109         return 0;
110 }
111
112 static int sanitize_rrd_update_data(char *data)
113 {
114         char clean_data[RRD_DATA_MAX_LEN];
115         int entries = 0;
116         int minus;
117         char *src, *end, *cln;
118
119         data[RRD_DATA_MAX_LEN - 1] = 0;
120         src = data;
121         cln = clean_data;
122
123         /*
124          * Copy a legit floating point number to clean_data buffer
125          * starting from *src and ending to next ':'. If no legit
126          * number could be found, put a 'U' there instead to make
127          * rrdtool to understand this datapoint is undefined.
128          */
129
130         while (src < data + RRD_DATA_MAX_LEN && *src) {
131                 minus = 0;
132
133                 /* skip any non_numbers but not ':' */
134                 while (*src && !isdigit(*src) && *src != '-' && *src != ':')
135                         src++;
136
137                 if (*src == '-') {
138                         src++;
139                         minus = 1;
140                 }
141
142                 /* Now find the end of the number */
143                 end = skip_numbers(src);
144
145                 /* Floating point numberrs may have a dot with more numbers */
146                 if (*end == '.') {
147                         end++;
148                         end = skip_numbers(end);
149                 }
150
151                 /*
152                  * Now we have gone past the number, there should be a
153                  * colon or zero byte. If src == end, there was no
154                  * number and the entry is undefined instead.
155                  */
156                 if ((*end == ':' || !*end) && src != end) {
157                         if (minus) {
158                                 *cln = '-';
159                                 cln++;
160                         }
161
162                         /*
163                          * Copy the legit number and start copying the
164                          * next one
165                          */
166                         for (; src <= end; src++, cln++)
167                                 *cln = *src;
168
169                         goto next;
170                 }
171
172                 /* Skip over whatever junk there might be */
173                 while (*end != ':' && *end)
174                         end++;
175
176                 /* Mark the entry as undefined */
177                 *cln = 'U';
178                 cln++;
179                 *cln = ':';
180                 cln++;
181         next:
182                 end++;
183                 src = end;
184                 entries++;
185         }
186
187         /*
188          * If last entry was undefined, we need to remove the extra
189          * colon at the end
190          */
191         if (*(cln - 1) == ':')
192                 cln--;
193         *cln = '\0';
194
195         strncpy(data, clean_data, RRD_DATA_MAX_LEN);
196         return entries;
197 }
198
199 static int write_to_logfile(struct rrd_database *rrd, const char *data, time_t now)
200 {
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(&now));
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(&now));
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 rrdtool_update_data_multi(struct rrd_database *rrd)
269 {
270         char **data = NULL;
271         int ret, i, d;
272         char cmd[] = RRDTOOL_CMD;
273         char *cmdline[512] = {
274                 RRDTOOL_CMD,
275                 "update",
276                 (char *const)rrd->filename,
277         };
278         int old_last_update = rrd->last_update;
279
280         ret = rrd->parser->parse_multi(&data, rrd->parser_data,
281                                 &rrd->parser_state, rrd->last_update);
282         if (ret < 0) {
283                 pr_err("Parser failure: %d\n", ret);
284                 goto out;
285         }
286
287         for (i = 3, d = 0; i < ARRAY_SIZE(cmdline) - 1; i++, d++) {
288                 time_t then;
289
290                 if (!data[d])
291                         break;
292
293                 sanitize_rrd_update_data(data[d]);
294
295                 then = atoi(data[d]);
296                 write_to_logfile(rrd, data[d], then);
297                 cmdline[i] = data[d];
298                 pr_info("Data: %s\n", data[d]);
299
300                 rrd->last_update = then;
301         }
302
303         cmdline[i] = 0;
304
305         if (ret)
306                 run(cmd, cmdline);
307
308 out:
309         if (data)
310                 for (d = 0; data[d]; d++)
311                         free(data[d]);
312         free(data);
313
314         if (old_last_update == rrd->last_update) {
315                 rrd->update_backoff = time(NULL) + 10;
316                 pr_info("Setting backoff\n");
317         } else
318                 rrd->update_backoff = 0;
319
320         /*
321          * Re-schedule job processing in case we are too far behind
322          * with updates on this database and can start parsing more
323          * data immediately.
324          */
325         notify_job_request();
326
327         return 0;
328 }
329
330 static int do_rrdtool_update_data(struct rrd_database *rrd)
331 {
332         char data[RRD_DATA_MAX_LEN + 12]; /* 12 == "%s:" + NULL termination */
333         char cmd[] = RRDTOOL_CMD;
334 //      char cmd[] = "echo";
335         char *const cmdline[] = {
336                 RRDTOOL_CMD,
337                 "update",
338                 (char *const)rrd->filename,
339                 data,
340                 0
341         };
342         int l;
343         time_t now = time(NULL);
344
345         bzero(data, sizeof(data));
346         l = sprintf(data, "%zd:", now);
347
348         if (rrd->parser && rrd->parser->parse_multi) {
349                 rrdtool_update_data_multi(rrd);
350         } else if (rrd->parser && rrd->parser->parse) {
351                 rrd->parser->parse(data + l, rrd->parser_data,
352                                 &rrd->parser_state);
353                 data[RRD_DATA_MAX_LEN + l] = '\0';
354
355                 pr_info("Data: %s\n", data);
356
357                 sanitize_rrd_update_data(data + l);
358                 write_to_logfile(rrd, data, now);
359
360                 run(cmd, cmdline);
361                 rrd->last_update = now;
362         } else
363                 rrd->last_update = now;
364
365         if (rrd->pre_draw_cmd && !strcmp(rrd->pre_draw_cmd[0], "shell")) {
366                 run(rrd->pre_draw_cmd[1], &rrd->pre_draw_cmd[1]);
367         }
368
369         if (rrd->images)
370                 rrdtool_draw_images(rrd->images);
371
372         /*
373          * We rely on the fact that rrdtool_draw_images queues image
374          * drawings into low priority queue and the post draw queue is
375          * placed on the queue after images. This ensures post draw
376          * command is not started before images are started.
377          *
378          * There is nothing that guarantees post_draw_cmd is executed
379          * after all images are completed though, but it's close..
380          */
381         if (rrd->post_draw_cmd)
382                 queue_work(WORK_PRIORITY_LOW, "rrdtool_post_draw_cmd",
383                         (work_fn_t *)run_post_draw_cmd, rrd);
384
385         rrd->update_active = 0;
386
387         return 0;
388 }
389
390 int rrdtool_update_data(struct rrd_database *rrd)
391 {
392         rrd->update_active = 1;
393
394         return queue_work(WORK_PRIORITY_HIGH, "rrdtool_update_data",
395                         (work_fn_t *)do_rrdtool_update_data, rrd);
396 }
397
398 /*
399  * Walk through the database list and return the first database which
400  * last update is too far in past
401  */
402 struct rrd_database *get_outdated_db(struct rrd_database **dblist)
403 {
404         int i;
405         time_t now = time(0), last;
406
407         for (i = 0; dblist[i]; i++) {
408                 last = max(ROUND_UP(dblist[i]->last_update, dblist[i]->interval),
409                         dblist[i]->update_backoff);
410                 if (!dblist[i]->update_active && last - now <= 0)
411                         return dblist[i];
412         }
413
414         /* Nothing to update this time, return null */
415         return NULL;
416 }
417
418 /*
419  * See how long we may sleep until next update interval window begins
420  */
421 int get_next_update(struct rrd_database **dblist, const char **name)
422 {
423         int i, sleeptime = 0, diff;
424         time_t now = time(0);
425
426         for (i = 0; dblist[i]; i++) {
427                 diff = ROUND_UP(dblist[i]->last_update, dblist[i]->interval) - now;
428                 diff = max(diff, dblist[i]->update_backoff - now);
429
430                 if (dblist[i]->update_active)
431                         diff = (now + dblist[i]->interval) % dblist[i]->interval;
432
433                 if (!sleeptime) {
434                         sleeptime = diff;
435                         *name = dblist[i]->name;
436                 }
437                 if (sleeptime > diff) {
438                         sleeptime = diff;
439                         *name = dblist[i]->name;
440                 }
441         }
442
443         if (sleeptime == 0)
444                 sleeptime = -1;
445
446         return sleeptime;
447 }
448
449 static int database_exists(struct rrd_database *db)
450 {
451         struct stat s;
452
453         /* If the filename exists, stat will return zero */
454         if (db->filename)
455                 return !stat(db->filename, &s);
456
457         return 0;
458 }
459
460 static int get_last_update(struct rrd_database *db)
461 {
462         char cmd[] = RRDTOOL_CMD;
463         char *args[10], argstr[ARGSTR_LEN];
464         char buf[16];
465         int idx = 0, argcnt = 0;
466         int ofd, efd, child;
467         int ret;
468
469         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
470         add_arg(args, argcnt, argstr, idx, "last");
471         add_arg(args, argcnt, argstr, idx, db->filename);
472
473         child = run_piped(cmd, args, NULL, &ofd, &efd);
474         ret = read(ofd, buf, sizeof(buf) - 1);
475         if (ret < 0) {
476                 pr_err("Error reading: %m\n");
477                 buf[0] = 0;
478         } else {
479                 buf[ret] = 0;
480         }
481
482         db->last_update = atoi(buf);
483         pr_info("Last update for %s is: %ld, %ld sec ago\n", db->name, db->last_update,
484                 time(NULL) - db->last_update);
485
486         clear_zombie(child);
487
488         return 0;
489 }
490
491 static int create_database(struct rrd_database *db)
492 {
493         char cmd[] = RRDTOOL_CMD;
494 //      char cmd[] = "echo";
495         char *args[512], argstr[ARGSTR_LEN];
496         int idx = 0, argcnt = 0;
497         int i;
498
499         if (!db->filename) {
500                 pr_err("Database %s missing database filename\n", db->name);
501                 return -1;
502         }
503
504         if (!db->sources || !db->archives) {
505                 pr_err("Cannot create db \"%s\", insufficient source data\n",
506                         db->filename);
507                 return -1;
508         }
509
510         add_arg(args, argcnt, argstr, idx, RRDTOOL_CMD);
511         add_arg(args, argcnt, argstr, idx, "create");
512         add_arg(args, argcnt, argstr, idx, "%s", db->filename);
513         add_arg(args, argcnt, argstr, idx, "--step");
514         add_arg(args, argcnt, argstr, idx, "%d", db->interval);
515
516         for (i = 0; db->sources[i].type; i++) {
517                 add_arg(args, argcnt, argstr, idx, "DS:%s:%s:%d:%f:%f",
518                         db->sources[i].name,
519                         db->sources[i].type,
520                         db->sources[i].heartbeat,
521                         db->sources[i].min,
522                         db->sources[i].max);
523         }
524
525         for (i = 0; db->archives[i].type; i++) {
526                 add_arg(args, argcnt, argstr, idx, "RRA:%s:%f:%d:%d",
527                         db->archives[i].type,
528                         db->archives[i].xff,
529                         db->archives[i].steps,
530                         db->archives[i].rows);
531         }
532
533         run(cmd, args);
534
535         return 0;
536 }
537
538 int rrdtool_create_missing_databases(struct rrd_database *dbs[])
539 {
540         struct rrd_database *db;
541         int i, ret = 0;
542
543         for (i = 0, db = dbs[i]; db; i++, db = dbs[i]) {
544                 if (database_exists(db)) {
545                         pr_info("database %s found\n", db->filename);
546                         get_last_update(db);
547                         continue;
548                 }
549                 pr_info("Database %s missing, creating\n", db->filename);
550                 ret |= create_database(db);
551         }
552
553         return ret;
554 }