06a0ed1a427c27798972d0c9755b57f1f242e3e2
[rrdtool.git] / src / rrd_graph_helper.c
1 /****************************************************************************
2  * RRDtool 1.2.23  Copyright by Tobi Oetiker, 1997-2007
3  ****************************************************************************
4  * rrd_graph_helper.c  commandline parser functions 
5  *                     this code initially written by Alex van den Bogaerdt
6  ****************************************************************************/
7
8 #include "rrd_graph.h"
9
10 #define dprintf if (gdp->debug) printf
11
12 /* NOTE ON PARSING:
13  *
14  * we use the following:
15  * 
16  * i=0;  sscanf(&line[*eaten], "what to find%n", variables, &i)
17  *
18  * Usually you want to find a separator as well.  Example:
19  * i=0; sscanf(&line[*eaten], "%li:%n", &someint, &i)
20  *
21  * When the separator is not found, i is not set and thus remains zero.
22  * Another way would be to compare strlen() to i
23  *
24  * Why is this important?  Because 12345abc should not be matched as
25  * integer 12345 ...
26  */
27
28 /* NOTE ON VNAMES:
29  *
30  * "if ((gdp->vidx=find_var(im, l))!=-1)" is not good enough, at least
31  * not by itself.
32  *
33  * A vname as a result of a VDEF is quite different from a vname
34  * resulting of a DEF or CDEF.
35  */
36
37 /* NOTE ON VNAMES:
38  *
39  * A vname called "123" is not to be parsed as the number 123
40  */
41
42
43 /* Define prototypes for the parsing methods.
44   Inputs:
45    const char *const line    - a fixed pointer to a fixed string
46    unsigned int *const eaten - a fixed pointer to a changing index in that line
47    graph_desc_t *const gdp   - a fixed pointer to a changing graph description
48    image_desc_t *const im    - a fixed pointer to a changing image description
49 */
50
51 int       rrd_parse_find_gf(
52     const char *const,
53     unsigned int *const,
54     graph_desc_t * const);
55 int       rrd_parse_legend(
56     const char *const,
57     unsigned int *const,
58     graph_desc_t * const);
59 int       rrd_parse_color(
60     const char *const,
61     graph_desc_t * const);
62 int       rrd_parse_CF(
63     const char *const,
64     unsigned int *const,
65     graph_desc_t * const,
66     enum cf_en *const);
67 int       rrd_parse_print(
68     const char *const,
69     unsigned int *const,
70     graph_desc_t * const,
71     image_desc_t * const);
72 int       rrd_parse_shift(
73     const char *const,
74     unsigned int *const,
75     graph_desc_t * const,
76     image_desc_t * const);
77 int       rrd_parse_xport(
78     const char *const,
79     unsigned int *const,
80     graph_desc_t * const,
81     image_desc_t * const);
82 int       rrd_parse_PVHLAST(
83     const char *const,
84     unsigned int *const,
85     graph_desc_t * const,
86     image_desc_t * const);
87 int       rrd_parse_make_vname(
88     const char *const,
89     unsigned int *const,
90     graph_desc_t * const,
91     image_desc_t * const);
92 int       rrd_parse_find_vname(
93     const char *const,
94     unsigned int *const,
95     graph_desc_t * const,
96     image_desc_t * const);
97 int       rrd_parse_def(
98     const char *const,
99     unsigned int *const,
100     graph_desc_t * const,
101     image_desc_t * const);
102 int       rrd_parse_vdef(
103     const char *const,
104     unsigned int *const,
105     graph_desc_t * const,
106     image_desc_t * const);
107 int       rrd_parse_cdef(
108     const char *const,
109     unsigned int *const,
110     graph_desc_t * const,
111     image_desc_t * const);
112
113
114
115 int rrd_parse_find_gf(
116     const char *const line,
117     unsigned int *const eaten,
118     graph_desc_t * const gdp)
119 {
120     char      funcname[11], c1 = 0;
121     int       i = 0;
122
123     /* start an argument with DEBUG to be able to see how it is parsed */
124     sscanf(&line[*eaten], "DEBUG%n", &i);
125     if (i) {
126         gdp->debug = 1;
127         (*eaten) += i;
128         i = 0;
129         dprintf("Scanning line '%s'\n", &line[*eaten]);
130     }
131     i = 0;
132     c1 = '\0';
133     sscanf(&line[*eaten], "%10[A-Z]%n%c", funcname, &i, &c1);
134     if (!i) {
135         rrd_set_error("Could not make sense out of '%s'", line);
136         return 1;
137     }
138     (*eaten) += i;
139     if ((int) (gdp->gf = gf_conv(funcname)) == -1) {
140         rrd_set_error("'%s' is not a valid function name", funcname);
141         return 1;
142     } else {
143         dprintf("- found function name '%s'\n", funcname);
144     }
145
146     if (c1 == '\0') {
147         rrd_set_error("Function %s needs parameters.  Line: %s\n", funcname,
148                       line);
149         return 1;
150     }
151     if (c1 == ':')
152         (*eaten)++;
153
154     /* Some commands have a parameter before the colon
155      * (currently only LINE)
156      */
157     switch (gdp->gf) {
158     case GF_LINE:
159         if (c1 == ':') {
160             gdp->linewidth = 1;
161             dprintf("- - using default width of 1\n");
162         } else {
163             i = 0;
164             sscanf(&line[*eaten], "%lf:%n", &gdp->linewidth, &i);
165             if (!i) {
166                 rrd_set_error("Cannot parse line width '%s' in line '%s'\n",
167                               &line[*eaten], line);
168                 return 1;
169             } else {
170                 dprintf("- - scanned width %f\n", gdp->linewidth);
171                 if (isnan(gdp->linewidth)) {
172                     rrd_set_error
173                         ("LINE width '%s' is not a number in line '%s'\n",
174                          &line[*eaten], line);
175                     return 1;
176                 }
177                 if (isinf(gdp->linewidth)) {
178                     rrd_set_error
179                         ("LINE width '%s' is out of range in line '%s'\n",
180                          &line[*eaten], line);
181                     return 1;
182                 }
183                 if (gdp->linewidth < 0) {
184                     rrd_set_error
185                         ("LINE width '%s' is less than 0 in line '%s'\n",
186                          &line[*eaten], line);
187                     return 1;
188                 }
189             }
190             (*eaten) += i;
191         }
192         break;
193     default:
194         if (c1 == ':')
195             break;
196         rrd_set_error("Malformed '%s' command in line '%s'\n", &line[*eaten],
197                       line);
198         return 1;
199     }
200     if (line[*eaten] == '\0') {
201         rrd_set_error("Expected some arguments after '%s'\n", line);
202         return 1;
203     }
204     return 0;
205 }
206
207 int rrd_parse_legend(
208     const char *const line,
209     unsigned int *const eaten,
210     graph_desc_t * const gdp)
211 {
212     int       i;
213
214     if (line[*eaten] == '\0' || line[*eaten] == ':') {
215         dprintf("- no (or: empty) legend found\n");
216         return 0;
217     }
218
219     i = scan_for_col(&line[*eaten], FMT_LEG_LEN, gdp->legend);
220
221     (*eaten) += i;
222
223     if (line[*eaten] != '\0' && line[*eaten] != ':') {
224         rrd_set_error("Legend too long");
225         return 1;
226     } else {
227         return 0;
228     }
229 }
230
231 int rrd_parse_color(
232     const char *const string,
233     graph_desc_t * const gdp)
234 {
235     unsigned int r = 0, g = 0, b = 0, a = 0, i;
236
237     /* matches the following formats:
238      ** RGB
239      ** RGBA
240      ** RRGGBB
241      ** RRGGBBAA
242      */
243
244     i = 0;
245     while (string[i] && isxdigit((unsigned int) string[i]))
246         i++;
247     if (string[i] != '\0')
248         return 1;       /* garbage follows hexdigits */
249     switch (i) {
250     case 3:
251     case 4:
252         sscanf(string, "%1x%1x%1x%1x", &r, &g, &b, &a);
253         r *= 0x11;
254         g *= 0x11;
255         b *= 0x11;
256         a *= 0x11;
257         if (i == 3)
258             a = 0xFF;
259         break;
260     case 6:
261     case 8:
262         sscanf(string, "%02x%02x%02x%02x", &r, &g, &b, &a);
263         if (i == 6)
264             a = 0xFF;
265         break;
266     default:
267         return 1;       /* wrong number of digits */
268     }
269     gdp->col = r << 24 | g << 16 | b << 8 | a;
270     return 0;
271 }
272
273 int rrd_parse_CF(
274     const char *const line,
275     unsigned int *const eaten,
276     graph_desc_t * const gdp,
277     enum cf_en *cf)
278 {
279     char      symname[CF_NAM_SIZE];
280     int       i = 0;
281
282     sscanf(&line[*eaten], CF_NAM_FMT "%n", symname, &i);
283     if ((!i) || ((line[(*eaten) + i] != '\0') && (line[(*eaten) + i] != ':'))) {
284         rrd_set_error("Cannot parse CF in '%s'", line);
285         return 1;
286     }
287     (*eaten) += i;
288     dprintf("- using CF '%s'\n", symname);
289
290     if ((int) (*cf = cf_conv(symname)) == -1) {
291         rrd_set_error("Unknown CF '%s' in '%s'", symname, line);
292         return 1;
293     }
294
295     if (line[*eaten] != '\0')
296         (*eaten)++;
297     return 0;
298 }
299
300 /* Try to match next token as a vname.
301  *
302  * Returns:
303  * -1     an error occured and the error string is set
304  * other  the vname index number
305  *
306  * *eaten is incremented only when a vname is found.
307  */
308 int rrd_parse_find_vname(
309     const char *const line,
310     unsigned int *const eaten,
311     graph_desc_t * const gdp,
312     image_desc_t * const im)
313 {
314     char      tmpstr[MAX_VNAME_LEN + 1];
315     int       i;
316     long      vidx;
317
318     i = 0;
319     sscanf(&line[*eaten], DEF_NAM_FMT "%n", tmpstr, &i);
320     if (!i) {
321         rrd_set_error("Could not parse line '%s'", line);
322         return -1;
323     }
324     if (line[*eaten + i] != ':' && line[*eaten + i] != '\0') {
325         rrd_set_error("Could not parse line '%s'", line);
326         return -1;
327     }
328     dprintf("- Considering '%s'\n", tmpstr);
329
330     if ((vidx = find_var(im, tmpstr)) < 0) {
331         dprintf("- Not a vname\n");
332         rrd_set_error("Not a valid vname: %s in line %s", tmpstr, line);
333         return -1;
334     }
335     dprintf("- Found vname '%s' vidx '%li'\n", tmpstr, gdp->vidx);
336     if (line[*eaten + i] == ':')
337         i++;
338     (*eaten) += i;
339     return vidx;
340 }
341
342 /* Parsing old-style xPRINT and new-style xPRINT */
343 int rrd_parse_print(
344     const char *const line,
345     unsigned int *const eaten,
346     graph_desc_t * const gdp,
347     image_desc_t * const im)
348 {
349     /* vname:CF:format in case of DEF-based vname
350      ** vname:CF:format in case of CDEF-based vname
351      ** vname:format[:strftime] in case of VDEF-based vname
352      */
353     if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
354         return 1;
355
356     switch (im->gdes[gdp->vidx].gf) {
357     case GF_DEF:
358     case GF_CDEF:
359         dprintf("- vname is of type DEF or CDEF, looking for CF\n");
360         if (rrd_parse_CF(line, eaten, gdp, &gdp->cf))
361             return 1;
362         break;
363     case GF_VDEF:
364         dprintf("- vname is of type VDEF\n");
365         break;
366     default:
367         rrd_set_error("Encountered unknown type variable '%s'",
368                       im->gdes[gdp->vidx].vname);
369         return 1;
370     }
371
372     if (rrd_parse_legend(line, eaten, gdp))
373         return 1;
374     /* for *PRINT the legend itself gets rendered later. We only
375        get the format at this juncture */
376     strcpy(gdp->format, gdp->legend);
377     gdp->legend[0] = '\0';
378     /* this is a very crud test, parsing :style flags should be in a function */
379     if (im->gdes[gdp->vidx].gf == GF_VDEF
380         && strcmp(line + (*eaten), ":strftime") == 0) {
381         gdp->strftm = 1;
382         (*eaten) += strlen(":strftime");
383     }
384     return 0;
385 }
386
387 /* SHIFT:_def_or_cdef:_vdef_or_number_
388  */
389 int rrd_parse_shift(
390     const char *const line,
391     unsigned int *const eaten,
392     graph_desc_t * const gdp,
393     image_desc_t * const im)
394 {
395     int       i;
396
397     if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
398         return 1;
399
400     switch (im->gdes[gdp->vidx].gf) {
401     case GF_DEF:
402     case GF_CDEF:
403         dprintf("- vname is of type DEF or CDEF, OK\n");
404         break;
405     case GF_VDEF:
406         rrd_set_error("Cannot shift a VDEF: '%s' in line '%s'\n",
407                       im->gdes[gdp->vidx].vname, line);
408         return 1;
409     default:
410         rrd_set_error("Encountered unknown type variable '%s' in line '%s'",
411                       im->gdes[gdp->vidx].vname, line);
412         return 1;
413     }
414
415     if ((gdp->shidx = rrd_parse_find_vname(line, eaten, gdp, im)) >= 0) {
416         switch (im->gdes[gdp->shidx].gf) {
417         case GF_DEF:
418         case GF_CDEF:
419             rrd_set_error("Offset cannot be a (C)DEF: '%s' in line '%s'\n",
420                           im->gdes[gdp->shidx].vname, line);
421             return 1;
422         case GF_VDEF:
423             dprintf("- vname is of type VDEF, OK\n");
424             break;
425         default:
426             rrd_set_error
427                 ("Encountered unknown type variable '%s' in line '%s'",
428                  im->gdes[gdp->vidx].vname, line);
429             return 1;
430         }
431     } else {
432         rrd_clear_error();
433         i = 0;
434         sscanf(&line[*eaten], "%li%n", &gdp->shval, &i);
435         if (i != (int) strlen(&line[*eaten])) {
436             rrd_set_error("Not a valid offset: %s in line %s", &line[*eaten],
437                           line);
438             return 1;
439         }
440         (*eaten) += i;
441         dprintf("- offset is number %li\n", gdp->shval);
442         gdp->shidx = -1;
443     }
444     return 0;
445 }
446
447 /* XPORT:_def_or_cdef[:legend]
448  */
449 int rrd_parse_xport(
450     const char *const line,
451     unsigned int *const eaten,
452     graph_desc_t * const gdp,
453     image_desc_t * const im)
454 {
455     if ((gdp->vidx = rrd_parse_find_vname(line, eaten, gdp, im)) < 0)
456         return 1;
457
458     switch (im->gdes[gdp->vidx].gf) {
459     case GF_DEF:
460     case GF_CDEF:
461         dprintf("- vname is of type DEF or CDEF, OK\n");
462         break;
463     case GF_VDEF:
464         rrd_set_error("Cannot xport a VDEF: '%s' in line '%s'\n",
465                       im->gdes[gdp->vidx].vname, line);
466         return 1;
467     default:
468         rrd_set_error("Encountered unknown type variable '%s' in line '%s'",
469                       im->gdes[gdp->vidx].vname, line);
470         return 1;
471     }
472     dprintf("- looking for legend in '%s'\n", &line[*eaten]);
473     if (rrd_parse_legend(line, eaten, gdp))
474         return 1;
475     return 0;
476 }
477
478 /* Parsing of PART, VRULE, HRULE, LINE, AREA, STACK and TICK
479 ** is done in one function.
480 **
481 ** Stacking PART, VRULE, HRULE or TICK is not allowed.
482 **
483 ** If a number (which is valid to enter) is more than a
484 ** certain amount of characters, it is caught as an error.
485 ** While this is arguable, so is entering fixed numbers
486 ** with more than MAX_VNAME_LEN significant digits.
487 */
488 int rrd_parse_PVHLAST(
489     const char *const line,
490     unsigned int *const eaten,
491     graph_desc_t * const gdp,
492     image_desc_t * const im)
493 {
494     int       i, j, k;
495     int       colorfound = 0;
496     char      tmpstr[MAX_VNAME_LEN + 10];   /* vname#RRGGBBAA\0 */
497     static int spacecnt = 0;
498
499     if (spacecnt == 0) {
500         float     one_space = gfx_get_text_width(im->canvas, 0,
501                                                  im->
502                                                  text_prop[TEXT_PROP_LEGEND].
503                                                  font,
504                                                  im->
505                                                  text_prop[TEXT_PROP_LEGEND].
506                                                  size,
507                                                  im->tabwidth, "    ",
508                                                  0) / 4.0;
509         float     target_space = gfx_get_text_width(im->canvas, 0,
510                                                     im->
511                                                     text_prop
512                                                     [TEXT_PROP_LEGEND].font,
513                                                     im->
514                                                     text_prop
515                                                     [TEXT_PROP_LEGEND].size,
516                                                     im->tabwidth, "oo", 0);
517
518         spacecnt = target_space / one_space;
519         dprintf("- spacecnt: %i onespace: %f targspace: %f\n", spacecnt,
520                 one_space, target_space);
521     }
522
523
524     dprintf("- parsing '%s'\n", &line[*eaten]);
525
526     /* have simpler code in the drawing section */
527     if (gdp->gf == GF_STACK) {
528         gdp->stack = 1;
529     }
530
531     i = scan_for_col(&line[*eaten], MAX_VNAME_LEN + 9, tmpstr);
532     if (line[*eaten + i] != '\0' && line[*eaten + i] != ':') {
533         rrd_set_error("Cannot parse line '%s'", line);
534         return 1;
535     }
536
537     j = i;
538     while (j > 0 && tmpstr[j] != '#')
539         j--;
540
541     if (j) {
542         tmpstr[j] = '\0';
543     }
544     /* We now have:
545      * tmpstr[0]    containing vname
546      * tmpstr[j]    if j!=0 then containing color
547      * i            size of vname + color
548      * j            if j!=0 then size of vname
549      */
550
551     /* Number or vname ?
552      * If it is an existing vname, that's OK, provided that it is a
553      * valid type (need time for VRULE, not a float)
554      * Else see if it parses as a number.
555      */
556     dprintf("- examining string '%s'\n", tmpstr);
557     if ((gdp->vidx = find_var(im, tmpstr)) >= 0) {
558         dprintf("- found vname: '%s' vidx %li\n", tmpstr, gdp->vidx);
559         switch (gdp->gf) {
560 #ifdef WITH_PIECHART
561         case GF_PART:
562 #endif
563         case GF_VRULE:
564         case GF_HRULE:
565             if (im->gdes[gdp->vidx].gf != GF_VDEF) {
566                 rrd_set_error("Using vname %s of wrong type in line %s\n",
567                               im->gdes[gdp->gf].vname, line);
568                 return 1;
569             }
570             break;
571         default:;
572         }
573     } else {
574         dprintf("- it is not an existing vname\n");
575         switch (gdp->gf) {
576         case GF_VRULE:
577             k = 0;
578             sscanf(tmpstr, "%li%n", &gdp->xrule, &k);
579             if (((j != 0) && (k == j)) || ((j == 0) && (k == i))) {
580                 dprintf("- found time: %li\n", gdp->xrule);
581             } else {
582                 dprintf("- is is not a valid number: %li\n", gdp->xrule);
583                 rrd_set_error
584                     ("parameter '%s' does not represent time in line %s\n",
585                      tmpstr, line);
586                 return 1;
587             }
588         default:
589             k = 0;
590             sscanf(tmpstr, "%lf%n", &gdp->yrule, &k);
591             if (((j != 0) && (k == j)) || ((j == 0) && (k == i))) {
592                 dprintf("- found number: %f\n", gdp->yrule);
593             } else {
594                 dprintf("- is is not a valid number: %li\n", gdp->xrule);
595                 rrd_set_error
596                     ("parameter '%s' does not represent a number in line %s\n",
597                      tmpstr, line);
598                 return 1;
599             }
600         }
601     }
602
603     if (j) {
604         j++;
605         dprintf("- examining color '%s'\n", &tmpstr[j]);
606         if (rrd_parse_color(&tmpstr[j], gdp)) {
607             rrd_set_error("Could not parse color in '%s'", &tmpstr[j]);
608             return 1;
609         }
610         dprintf("- parsed color 0x%08x\n", (unsigned int) gdp->col);
611         colorfound = 1;
612     } else {
613         dprintf("- no color present in '%s'\n", tmpstr);
614     }
615
616     (*eaten) += i;      /* after vname#color */
617     if (line[*eaten] != '\0') {
618         (*eaten)++;     /* after colon */
619     }
620
621     if (gdp->gf == GF_TICK) {
622         dprintf("- parsing '%s'\n", &line[*eaten]);
623         dprintf("- looking for optional TICK number\n");
624         j = 0;
625         sscanf(&line[*eaten], "%lf%n", &gdp->yrule, &j);
626         if (j) {
627             if (line[*eaten + j] != '\0' && line[*eaten + j] != ':') {
628                 rrd_set_error("Cannot parse TICK fraction '%s'", line);
629                 return 1;
630             }
631             dprintf("- found number %f\n", gdp->yrule);
632             if (gdp->yrule > 1.0 || gdp->yrule < -1.0) {
633                 rrd_set_error("Tick factor should be <= 1.0");
634                 return 1;
635             }
636             (*eaten) += j;
637         } else {
638             dprintf("- not found, defaulting to 0.1\n");
639             gdp->yrule = 0.1;
640         }
641         if (line[*eaten] == '\0') {
642             dprintf("- done parsing line\n");
643             return 0;
644         } else {
645             if (line[*eaten] == ':') {
646                 (*eaten)++;
647             } else {
648                 rrd_set_error("Can't make sense of that TICK line");
649                 return 1;
650             }
651         }
652     }
653
654     dprintf("- parsing '%s'\n", &line[*eaten]);
655
656     /* Legend is next.  A legend without a color is an error.
657      ** Stacking an item without having a legend is OK however
658      ** then an empty legend should be specified.
659      **   LINE:val#color:STACK  means legend is string "STACK"
660      **   LINE:val#color::STACK means no legend, and do STACK
661      **   LINE:val:STACK        is an error (legend but no color)
662      **   LINE:val::STACK   means no legend, and do STACK
663      */
664     if (colorfound) {
665         int       err = 0;
666         char     *linecp = strdup(line);
667
668         dprintf("- looking for optional legend\n");
669
670         dprintf("- examining '%s'\n", &line[*eaten]);
671         if (linecp[*eaten] != '\0' && linecp[*eaten] != ':') {
672             int       spi;
673
674             /* If the legend is not empty, it has to be prefixed with spacecnt ' ' characters. This then gets
675              * replaced by the color box later on. */
676             for (spi = 0; spi < spacecnt && (*eaten) > 1; spi++) {
677                 linecp[--(*eaten)] = ' ';
678             }
679         }
680
681         if (rrd_parse_legend(linecp, eaten, gdp))
682             err = 1;
683         free(linecp);
684         if (err)
685             return 1;
686
687         dprintf("- found legend '%s'\n", &gdp->legend[2]);
688     } else {
689         dprintf("- skipping empty legend\n");
690         if (line[*eaten] != '\0' && line[*eaten] != ':') {
691             rrd_set_error("Legend set but no color: %s", &line[*eaten]);
692             return 1;
693         }
694     }
695     if (line[*eaten] == '\0') {
696         dprintf("- done parsing line\n");
697         return 0;
698     }
699     (*eaten)++;         /* after colon */
700
701     /* PART, HRULE, VRULE and TICK cannot be stacked. */
702     if ((gdp->gf == GF_HRULE)
703         || (gdp->gf == GF_VRULE)
704 #ifdef WITH_PIECHART
705         || (gdp->gf == GF_PART)
706 #endif
707         || (gdp->gf == GF_TICK)
708         )
709         return 0;
710
711     dprintf("- parsing '%s'\n", &line[*eaten]);
712     if (line[*eaten] != '\0') {
713         dprintf("- still more, should be STACK\n");
714         j = scan_for_col(&line[*eaten], 5, tmpstr);
715         if (line[*eaten + j] != '\0' && line[*eaten + j] != ':') {
716             /* not 5 chars */
717             rrd_set_error("Garbage found where STACK expected");
718             return 1;
719         }
720         if (!strcmp("STACK", tmpstr)) {
721             dprintf("- found STACK\n");
722             gdp->stack = 1;
723             (*eaten) += j;
724         } else {
725             rrd_set_error("Garbage found where STACK expected");
726             return 1;
727         }
728     }
729     if (line[*eaten] == '\0') {
730         dprintf("- done parsing line\n");
731         return 0;
732     }
733     (*eaten)++;
734     dprintf("- parsing '%s'\n", &line[*eaten]);
735
736     return 0;
737 }
738
739 int rrd_parse_make_vname(
740     const char *const line,
741     unsigned int *const eaten,
742     graph_desc_t * const gdp,
743     image_desc_t * const im)
744 {
745     char      tmpstr[MAX_VNAME_LEN + 10];
746     int       i = 0;
747
748     sscanf(&line[*eaten], DEF_NAM_FMT "=%n", tmpstr, &i);
749     if (!i) {
750         rrd_set_error("Cannot parse vname from '%s'", line);
751         return 1;
752     }
753     dprintf("- found candidate '%s'\n", tmpstr);
754
755     if ((gdp->vidx = find_var(im, tmpstr)) >= 0) {
756         rrd_set_error("Attempting to reuse '%s'", im->gdes[gdp->vidx].vname);
757         return 1;
758     }
759     strcpy(gdp->vname, tmpstr);
760     dprintf("- created vname '%s' vidx %lu\n", gdp->vname, im->gdes_c - 1);
761     (*eaten) += i;
762     return 0;
763 }
764
765 int rrd_parse_def(
766     const char *const line,
767     unsigned int *const eaten,
768     graph_desc_t * const gdp,
769     image_desc_t * const im)
770 {
771     int       i = 0;
772     char      command[7];   /* step, start, end, reduce */
773     char      tmpstr[256];
774     struct rrd_time_value start_tv, end_tv;
775     time_t    start_tmp = 0, end_tmp = 0;
776     char     *parsetime_error = NULL;
777
778     start_tv.type = end_tv.type = ABSOLUTE_TIME;
779     start_tv.offset = end_tv.offset = 0;
780     localtime_r(&gdp->start, &start_tv.tm);
781     localtime_r(&gdp->end, &end_tv.tm);
782
783     dprintf("- parsing '%s'\n", &line[*eaten]);
784     dprintf("- from line '%s'\n", line);
785
786     if (rrd_parse_make_vname(line, eaten, gdp, im))
787         return 1;
788     i = scan_for_col(&line[*eaten], sizeof(gdp->rrd) - 1, gdp->rrd);
789     if (line[*eaten + i] != ':') {
790         rrd_set_error("Problems reading database name");
791         return 1;
792     }
793     (*eaten) += ++i;
794     dprintf("- using file '%s'\n", gdp->rrd);
795
796     i = 0;
797     sscanf(&line[*eaten], DS_NAM_FMT ":%n", gdp->ds_nam, &i);
798     if (!i) {
799         rrd_set_error("Cannot parse DS in '%s'", line);
800         return 1;
801     }
802     (*eaten) += i;
803     dprintf("- using DS '%s'\n", gdp->ds_nam);
804
805     if (rrd_parse_CF(line, eaten, gdp, &gdp->cf))
806         return 1;
807     gdp->cf_reduce = gdp->cf;
808
809     if (line[*eaten] == '\0')
810         return 0;
811
812     while (1) {
813         dprintf("- optional parameter follows: %s\n", &line[*eaten]);
814         i = 0;
815         sscanf(&line[*eaten], "%6[a-z]=%n", command, &i);
816         if (!i) {
817             rrd_set_error("Parse error in '%s'", line);
818             return 1;
819         }
820         (*eaten) += i;
821         dprintf("- processing '%s'\n", command);
822         if (!strcmp("reduce", command)) {
823             if (rrd_parse_CF(line, eaten, gdp, &gdp->cf_reduce))
824                 return 1;
825             if (line[*eaten] != '\0')
826                 (*eaten)--;
827         } else if (!strcmp("step", command)) {
828             i = 0;
829             sscanf(&line[*eaten], "%lu%n", &gdp->step, &i);
830             gdp->step_orig = gdp->step;
831             (*eaten) += i;
832             dprintf("- using step %lu\n", gdp->step);
833         } else if (!strcmp("start", command)) {
834             i = scan_for_col(&line[*eaten], 255, tmpstr);
835             (*eaten) += i;
836             if ((parsetime_error = parsetime(tmpstr, &start_tv))) {
837                 rrd_set_error("start time: %s", parsetime_error);
838                 return 1;
839             }
840             dprintf("- done parsing:  '%s'\n", &line[*eaten]);
841         } else if (!strcmp("end", command)) {
842             i = scan_for_col(&line[*eaten], 255, tmpstr);
843             (*eaten) += i;
844             if ((parsetime_error = parsetime(tmpstr, &end_tv))) {
845                 rrd_set_error("end time: %s", parsetime_error);
846                 return 1;
847             }
848             dprintf("- done parsing:  '%s'\n", &line[*eaten]);
849         } else {
850             rrd_set_error("Parse error in '%s'", line);
851             return 1;
852         }
853         if (line[*eaten] == '\0')
854             break;
855         if (line[*eaten] != ':') {
856             dprintf("- Expected to see end of string but got '%s'\n",
857                     &line[*eaten]);
858             rrd_set_error("Parse error in '%s'", line);
859             return 1;
860         }
861         (*eaten)++;
862     }
863     if (proc_start_end(&start_tv, &end_tv, &start_tmp, &end_tmp) == -1) {
864         /* error string is set in parsetime.c */
865         return 1;
866     }
867     if (start_tmp < 3600 * 24 * 365 * 10) {
868         rrd_set_error("the first entry to fetch should be "
869                       "after 1980 (%ld)", start_tmp);
870         return 1;
871     }
872
873     if (end_tmp < start_tmp) {
874         rrd_set_error("start (%ld) should be less than end (%ld)",
875                       start_tmp, end_tmp);
876         return 1;
877     }
878
879     gdp->start = start_tmp;
880     gdp->end = end_tmp;
881     gdp->start_orig = start_tmp;
882     gdp->end_orig = end_tmp;
883
884     dprintf("- start time %lu\n", gdp->start);
885     dprintf("- end   time %lu\n", gdp->end);
886
887     return 0;
888 }
889
890 int rrd_parse_vdef(
891     const char *const line,
892     unsigned int *const eaten,
893     graph_desc_t * const gdp,
894     image_desc_t * const im)
895 {
896     char      tmpstr[MAX_VNAME_LEN + 1];    /* vname\0 */
897     int       i = 0;
898
899     dprintf("- parsing '%s'\n", &line[*eaten]);
900     if (rrd_parse_make_vname(line, eaten, gdp, im))
901         return 1;
902
903     sscanf(&line[*eaten], DEF_NAM_FMT ",%n", tmpstr, &i);
904     if (!i) {
905         rrd_set_error("Cannot parse line '%s'", line);
906         return 1;
907     }
908     if ((gdp->vidx = find_var(im, tmpstr)) < 0) {
909         rrd_set_error("Not a valid vname: %s in line %s", tmpstr, line);
910         return 1;
911     }
912     if (im->gdes[gdp->vidx].gf != GF_DEF && im->gdes[gdp->vidx].gf != GF_CDEF) {
913         rrd_set_error("variable '%s' not DEF nor "
914                       "CDEF in VDEF '%s'", tmpstr, gdp->vname);
915         return 1;
916     }
917     dprintf("- found vname: '%s' vidx %li\n", tmpstr, gdp->vidx);
918     (*eaten) += i;
919
920     dprintf("- calling vdef_parse with param '%s'\n", &line[*eaten]);
921     vdef_parse(gdp, &line[*eaten]);
922     while (line[*eaten] != '\0' && line[*eaten] != ':')
923         (*eaten)++;
924
925     return 0;
926 }
927
928 int rrd_parse_cdef(
929     const char *const line,
930     unsigned int *const eaten,
931     graph_desc_t * const gdp,
932     image_desc_t * const im)
933 {
934     dprintf("- parsing '%s'\n", &line[*eaten]);
935     if (rrd_parse_make_vname(line, eaten, gdp, im))
936         return 1;
937     if ((gdp->rpnp = rpn_parse((void *) im, &line[*eaten], &find_var_wrapper)
938         ) == NULL) {
939         rrd_set_error("invalid rpn expression in: %s", &line[*eaten]);
940         return 1;
941     };
942     while (line[*eaten] != '\0' && line[*eaten] != ':')
943         (*eaten)++;
944     return 0;
945 }
946
947 void rrd_graph_script(
948     int argc,
949     char *argv[],
950     image_desc_t * const im,
951     int optno)
952 {
953     int       i;
954
955     /* save state for STACK backward compat function */
956     enum gf_en last_gf = GF_PRINT;
957     float     last_linewidth = 0.0;
958
959     for (i = optind + optno; i < argc; i++) {
960         graph_desc_t *gdp;
961         unsigned int eaten = 0;
962
963         if (gdes_alloc(im))
964             return;     /* the error string is already set */
965         gdp = &im->gdes[im->gdes_c - 1];
966 #ifdef DEBUG
967         gdp->debug = 1;
968 #endif
969
970         if (rrd_parse_find_gf(argv[i], &eaten, gdp))
971             return;
972
973         switch (gdp->gf) {
974         case GF_SHIFT: /* vname:value */
975             if (rrd_parse_shift(argv[i], &eaten, gdp, im))
976                 return;
977             break;
978         case GF_XPORT:
979             if (rrd_parse_xport(argv[i], &eaten, gdp, im))
980                 return;
981             break;
982         case GF_PRINT: /* vname:CF:format -or- vname:format */
983             im->prt_c++;
984         case GF_GPRINT:    /* vname:CF:format -or- vname:format */
985             if (rrd_parse_print(argv[i], &eaten, gdp, im))
986                 return;
987             break;
988         case GF_COMMENT:   /* text */
989             if (rrd_parse_legend(argv[i], &eaten, gdp))
990                 return;
991             break;
992 #ifdef WITH_PIECHART
993         case GF_PART:  /* value[#color[:legend]] */
994 #endif
995         case GF_VRULE: /* value#color[:legend] */
996         case GF_HRULE: /* value#color[:legend] */
997         case GF_LINE:  /* vname-or-value[#color[:legend]][:STACK] */
998         case GF_AREA:  /* vname-or-value[#color[:legend]][:STACK] */
999         case GF_TICK:  /* vname#color[:num[:legend]] */
1000             if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
1001                 return;
1002             last_gf = gdp->gf;
1003             last_linewidth = gdp->linewidth;
1004             break;
1005         case GF_STACK: /* vname-or-value[#color[:legend]] */
1006             if (rrd_parse_PVHLAST(argv[i], &eaten, gdp, im))
1007                 return;
1008             if (last_gf == GF_LINE || last_gf == GF_AREA) {
1009                 gdp->gf = last_gf;
1010                 gdp->linewidth = last_linewidth;
1011             } else {
1012                 rrd_set_error("STACK must follow LINE or AREA! command:\n%s",
1013                               &argv[i][eaten], argv[i]);
1014                 return;
1015             }
1016             break;
1017             /* data acquisition */
1018         case GF_DEF:   /* vname=x:DS:CF:[:step=#][:start=#][:end=#] */
1019             if (rrd_parse_def(argv[i], &eaten, gdp, im))
1020                 return;
1021             break;
1022         case GF_CDEF:  /* vname=rpn-expression */
1023             if (rrd_parse_cdef(argv[i], &eaten, gdp, im))
1024                 return;
1025             break;
1026         case GF_VDEF:  /* vname=rpn-expression */
1027             if (rrd_parse_vdef(argv[i], &eaten, gdp, im))
1028                 return;
1029             break;
1030         }
1031         if (gdp->debug) {
1032             dprintf("used %i out of %i chars\n", eaten, strlen(argv[i]));
1033             dprintf("parsed line: '%s'\n", argv[i]);
1034             dprintf("remaining: '%s'\n", &argv[i][eaten]);
1035             if (eaten >= strlen(argv[i]))
1036                 dprintf("Command finished successfully\n");
1037         }
1038         if (eaten < strlen(argv[i])) {
1039             rrd_set_error("Garbage '%s' after command:\n%s",
1040                           &argv[i][eaten], argv[i]);
1041             return;
1042         }
1043     }
1044 }