fixed setlocale issues
[rrdtool.git] / src / rrd_graph.c
1 /****************************************************************************
2  * RRDtool 1.1.x  Copyright Tobias Oetiker, 1997 - 2002
3  ****************************************************************************
4  * rrd__graph.c  make creates ne rrds
5  ****************************************************************************/
6
7
8 #include <sys/stat.h>
9 #ifdef WIN32
10 #include <io.h>
11 #include <fcntl.h>
12 #endif
13
14 #ifdef HAVE_TIME_H
15 #include <time.h>
16 #endif
17
18 #ifdef HAVE_LOCALE_H
19 #include <locale.h>
20 #endif
21
22 #include "rrd_tool.h"
23
24 #include "rrd_graph.h"
25 #include "rrd_graph_helper.h"
26
27 /* some constant definitions */
28
29
30 #ifndef RRD_DEFAULT_FONT
31 #ifdef WIN32
32 #define RRD_DEFAULT_FONT "c:/winnt/fonts/COUR.TTF"
33 #else
34 #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/openoffice/ariosor.ttf" 
35 /* #define RRD_DEFAULT_FONT "/usr/share/fonts/truetype/Arial.ttf" */
36 #endif
37 #endif
38
39
40 text_prop_t text_prop[] = {   
41      { 10.0, RRD_DEFAULT_FONT }, /* default */
42      { 12.0, RRD_DEFAULT_FONT }, /* title */
43      { 8.0,  RRD_DEFAULT_FONT },  /* axis */
44      { 10.0, RRD_DEFAULT_FONT },  /* unit */
45      { 10.0, RRD_DEFAULT_FONT }  /* legend */
46 };
47
48 xlab_t xlab[] = {
49     {0,        TMT_SECOND,30, TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
50     {2,        TMT_MINUTE,1,  TMT_MINUTE,5,  TMT_MINUTE,5,         0,"%H:%M"},
51     {5,        TMT_MINUTE,2,  TMT_MINUTE,10, TMT_MINUTE,10,        0,"%H:%M"},
52     {10,       TMT_MINUTE,5,  TMT_MINUTE,20, TMT_MINUTE,20,        0,"%H:%M"},
53     {30,       TMT_MINUTE,10, TMT_HOUR,1,    TMT_HOUR,1,           0,"%H:%M"},
54     {60,       TMT_MINUTE,30, TMT_HOUR,2,    TMT_HOUR,2,           0,"%H:%M"},
55     {180,      TMT_HOUR,1,    TMT_HOUR,6,    TMT_HOUR,6,           0,"%H:%M"},
56     /*{300,      TMT_HOUR,3,    TMT_HOUR,12,   TMT_HOUR,12,    12*3600,"%a %p"},  this looks silly*/
57     {600,      TMT_HOUR,6,    TMT_DAY,1,     TMT_DAY,1,      24*3600,"%a"},
58     {1800,     TMT_HOUR,12,   TMT_DAY,1,     TMT_DAY,2,      24*3600,"%a"},
59     {3600,     TMT_DAY,1,     TMT_WEEK,1,     TMT_WEEK,1,    7*24*3600,"Week %V"},
60     {3*3600,   TMT_WEEK,1,      TMT_MONTH,1,     TMT_WEEK,2,    7*24*3600,"Week %V"},
61     {6*3600,   TMT_MONTH,1,   TMT_MONTH,1,   TMT_MONTH,1, 30*24*3600,"%b"},
62     {48*3600,  TMT_MONTH,1,   TMT_MONTH,3,   TMT_MONTH,3, 30*24*3600,"%b"},
63     {10*24*3600, TMT_YEAR,1,  TMT_YEAR,1,    TMT_YEAR,1, 365*24*3600,"%y"},
64     {-1,TMT_MONTH,0,TMT_MONTH,0,TMT_MONTH,0,0,""}
65 };
66
67 /* sensible logarithmic y label intervals ...
68    the first element of each row defines the possible starting points on the
69    y axis ... the other specify the */
70
71 double yloglab[][12]= {{ 1e9, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
72                        {  1e3, 1,  0,  0,  0,   0,  0,  0,  0,  0,  0,  0 },
73                        {  1e1, 1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
74                        /* {  1e1, 1,  5,  0,  0,  0,  0,  0,  0,  0,  0,  0 }, */
75                        {  1e1, 1,  2.5,  5,  7.5,  0,  0,  0,  0,  0,  0,  0 },
76                        {  1e1, 1,  2,  4,  6,  8,  0,  0,  0,  0,  0,  0 },
77                        {  1e1, 1,  2,  3,  4,  5,  6,  7,  8,  9,  0,  0 },
78                        {  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }};
79
80 /* sensible y label intervals ...*/
81
82 ylab_t ylab[]= {
83     {0.1, {1,2, 5,10}},
84     {0.2, {1,5,10,20}},
85     {0.5, {1,2, 4,10}},
86     {1.0,   {1,2, 5,10}},
87     {2.0,   {1,5,10,20}},
88     {5.0,   {1,2, 4,10}},
89     {10.0,  {1,2, 5,10}},
90     {20.0,  {1,5,10,20}},
91     {50.0,  {1,2, 4,10}},
92     {100.0, {1,2, 5,10}},
93     {200.0, {1,5,10,20}},
94     {500.0, {1,2, 4,10}},
95     {0.0,   {0,0,0,0}}};
96
97
98 gfx_color_t graph_col[] =   /* default colors */
99 {    0xFFFFFFFF,   /* canvas     */
100      0xF0F0F0FF,   /* background */
101      0xD0D0D0FF,   /* shade A    */
102      0xA0A0A0FF,   /* shade B    */
103      0x909090FF,   /* grid       */
104      0xE05050FF,   /* major grid */
105      0x000000FF,   /* font       */ 
106      0x000000FF,   /* frame      */
107      0xFF0000FF  /* arrow      */
108 };
109
110
111 /* #define DEBUG */
112
113 #ifdef DEBUG
114 # define DPRINT(x)    (void)(printf x, printf("\n"))
115 #else
116 # define DPRINT(x)
117 #endif
118
119
120 /* initialize with xtr(im,0); */
121 int
122 xtr(image_desc_t *im,time_t mytime){
123     static double pixie;
124     if (mytime==0){
125         pixie = (double) im->xsize / (double)(im->end - im->start);
126         return im->xorigin;
127     }
128     return (int)((double)im->xorigin 
129                  + pixie * ( mytime - im->start ) );
130 }
131
132 /* translate data values into y coordinates */
133 double
134 ytr(image_desc_t *im, double value){
135     static double pixie;
136     double yval;
137     if (isnan(value)){
138       if(!im->logarithmic)
139         pixie = (double) im->ysize / (im->maxval - im->minval);
140       else 
141         pixie = (double) im->ysize / (log10(im->maxval) - log10(im->minval));
142       yval = im->yorigin;
143     } else if(!im->logarithmic) {
144       yval = im->yorigin - pixie * (value - im->minval);
145     } else {
146       if (value < im->minval) {
147         yval = im->yorigin;
148       } else {
149         yval = im->yorigin - pixie * (log10(value) - log10(im->minval));
150       }
151     }
152     /* make sure we don't return anything too unreasonable. GD lib can
153        get terribly slow when drawing lines outside its scope. This is 
154        especially problematic in connection with the rigid option */
155     if (! im->rigid) {
156       /* keep yval as-is */
157     } else if (yval > im->yorigin) {
158       yval = im->yorigin+2;
159     } else if (yval < im->yorigin - im->ysize){
160       yval = im->yorigin - im->ysize - 2;
161     } 
162     return yval;
163 }
164
165
166
167 /* conversion function for symbolic entry names */
168
169
170 #define conv_if(VV,VVV) \
171    if (strcmp(#VV, string) == 0) return VVV ;
172
173 enum gf_en gf_conv(char *string){
174     
175     conv_if(PRINT,GF_PRINT)
176     conv_if(GPRINT,GF_GPRINT)
177     conv_if(COMMENT,GF_COMMENT)
178     conv_if(HRULE,GF_HRULE)
179     conv_if(VRULE,GF_VRULE)
180     conv_if(LINE,GF_LINE)
181     conv_if(AREA,GF_AREA)
182     conv_if(STACK,GF_STACK)
183     conv_if(TICK,GF_TICK)
184     conv_if(DEF,GF_DEF)
185     conv_if(CDEF,GF_CDEF)
186     conv_if(VDEF,GF_VDEF)
187     conv_if(PART,GF_PART)
188     
189     return (-1);
190 }
191
192 enum gfx_if_en if_conv(char *string){
193     
194     conv_if(PNG,IF_PNG)
195     conv_if(SVG,IF_SVG)
196     conv_if(EPS,IF_EPS)
197
198     return (-1);
199 }
200
201 enum tmt_en tmt_conv(char *string){
202
203     conv_if(SECOND,TMT_SECOND)
204     conv_if(MINUTE,TMT_MINUTE)
205     conv_if(HOUR,TMT_HOUR)
206     conv_if(DAY,TMT_DAY)
207     conv_if(WEEK,TMT_WEEK)
208     conv_if(MONTH,TMT_MONTH)
209     conv_if(YEAR,TMT_YEAR)
210     return (-1);
211 }
212
213 enum grc_en grc_conv(char *string){
214
215     conv_if(BACK,GRC_BACK)
216     conv_if(CANVAS,GRC_CANVAS)
217     conv_if(SHADEA,GRC_SHADEA)
218     conv_if(SHADEB,GRC_SHADEB)
219     conv_if(GRID,GRC_GRID)
220     conv_if(MGRID,GRC_MGRID)
221     conv_if(FONT,GRC_FONT)
222     conv_if(FRAME,GRC_FRAME)
223     conv_if(ARROW,GRC_ARROW)
224
225     return -1;  
226 }
227
228 enum text_prop_en text_prop_conv(char *string){
229       
230     conv_if(DEFAULT,TEXT_PROP_DEFAULT)
231     conv_if(TITLE,TEXT_PROP_TITLE)
232     conv_if(AXIS,TEXT_PROP_AXIS)
233     conv_if(UNIT,TEXT_PROP_UNIT)
234     conv_if(LEGEND,TEXT_PROP_LEGEND)
235     return -1;
236 }
237
238
239 #undef conv_if
240
241
242
243 int
244 im_free(image_desc_t *im)
245 {
246     long i,ii;
247     if (im == NULL) return 0;
248     for(i=0;i<im->gdes_c;i++){
249       if (im->gdes[i].data_first){
250         /* careful here, because a single pointer can occur several times */
251           free (im->gdes[i].data);
252           if (im->gdes[i].ds_namv){
253               for (ii=0;ii<im->gdes[i].ds_cnt;ii++)
254                   free(im->gdes[i].ds_namv[ii]);
255               free(im->gdes[i].ds_namv);
256           }
257       }
258       free (im->gdes[i].p_data);
259       free (im->gdes[i].rpnp);
260     }
261     free(im->gdes);
262     gfx_destroy(im->canvas);
263     return 0;
264 }
265
266 /* find SI magnitude symbol for the given number*/
267 void
268 auto_scale(
269            image_desc_t *im,   /* image description */
270            double *value,
271            char **symb_ptr,
272            double *magfact
273            )
274 {
275         
276     char *symbol[] = {"a", /* 10e-18 Atto */
277                       "f", /* 10e-15 Femto */
278                       "p", /* 10e-12 Pico */
279                       "n", /* 10e-9  Nano */
280                       "u", /* 10e-6  Micro */
281                       "m", /* 10e-3  Milli */
282                       " ", /* Base */
283                       "k", /* 10e3   Kilo */
284                       "M", /* 10e6   Mega */
285                       "G", /* 10e9   Giga */
286                       "T", /* 10e12  Tera */
287                       "P", /* 10e15  Peta */
288                       "E"};/* 10e18  Exa */
289
290     int symbcenter = 6;
291     int sindex;  
292
293     if (*value == 0.0 || isnan(*value) ) {
294         sindex = 0;
295         *magfact = 1.0;
296     } else {
297         sindex = floor(log(fabs(*value))/log((double)im->base)); 
298         *magfact = pow((double)im->base, (double)sindex);
299         (*value) /= (*magfact);
300     }
301     if ( sindex <= symbcenter && sindex >= -symbcenter) {
302         (*symb_ptr) = symbol[sindex+symbcenter];
303     }
304     else {
305         (*symb_ptr) = "?";
306     }
307 }
308
309
310 /* find SI magnitude symbol for the numbers on the y-axis*/
311 void 
312 si_unit(
313     image_desc_t *im   /* image description */
314 )
315 {
316
317     char symbol[] = {'a', /* 10e-18 Atto */ 
318                      'f', /* 10e-15 Femto */
319                      'p', /* 10e-12 Pico */
320                      'n', /* 10e-9  Nano */
321                      'u', /* 10e-6  Micro */
322                      'm', /* 10e-3  Milli */
323                      ' ', /* Base */
324                      'k', /* 10e3   Kilo */
325                      'M', /* 10e6   Mega */
326                      'G', /* 10e9   Giga */
327                      'T', /* 10e12  Tera */
328                      'P', /* 10e15  Peta */
329                      'E'};/* 10e18  Exa */
330
331     int   symbcenter = 6;
332     double digits;  
333     
334     if (im->unitsexponent != 9999) {
335         /* unitsexponent = 9, 6, 3, 0, -3, -6, -9, etc */
336         digits = floor(im->unitsexponent / 3);
337     } else {
338         digits = floor( log( max( fabs(im->minval),fabs(im->maxval)))/log((double)im->base)); 
339     }
340     im->magfact = pow((double)im->base , digits);
341
342 #ifdef DEBUG
343     printf("digits %6.3f  im->magfact %6.3f\n",digits,im->magfact);
344 #endif
345
346     if ( ((digits+symbcenter) < sizeof(symbol)) &&
347                     ((digits+symbcenter) >= 0) )
348         im->symbol = symbol[(int)digits+symbcenter];
349     else
350         im->symbol = ' ';
351  }
352
353 /*  move min and max values around to become sensible */
354
355 void 
356 expand_range(image_desc_t *im)
357 {
358     double sensiblevalues[] ={1000.0,900.0,800.0,750.0,700.0,
359                               600.0,500.0,400.0,300.0,250.0,
360                               200.0,125.0,100.0,90.0,80.0,
361                               75.0,70.0,60.0,50.0,40.0,30.0,
362                               25.0,20.0,10.0,9.0,8.0,
363                               7.0,6.0,5.0,4.0,3.5,3.0,
364                               2.5,2.0,1.8,1.5,1.2,1.0,
365                               0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0,-1};
366     
367     double scaled_min,scaled_max;  
368     double adj;
369     int i;
370     
371
372     
373 #ifdef DEBUG
374     printf("Min: %6.2f Max: %6.2f MagFactor: %6.2f\n",
375            im->minval,im->maxval,im->magfact);
376 #endif
377
378     if (isnan(im->ygridstep)){
379         if(im->extra_flags & ALTAUTOSCALE) {
380             /* measure the amplitude of the function. Make sure that
381                graph boundaries are slightly higher then max/min vals
382                so we can see amplitude on the graph */
383               double delt, fact;
384
385               delt = im->maxval - im->minval;
386               adj = delt * 0.1;
387               fact = 2.0 * pow(10.0,
388                     floor(log10(max(fabs(im->minval), fabs(im->maxval)))) - 2);
389               if (delt < fact) {
390                 adj = (fact - delt) * 0.55;
391 #ifdef DEBUG
392               printf("Min: %6.2f Max: %6.2f delt: %6.2f fact: %6.2f adj: %6.2f\n", im->minval, im->maxval, delt, fact, adj);
393 #endif
394               }
395               im->minval -= adj;
396               im->maxval += adj;
397         }
398         else if(im->extra_flags & ALTAUTOSCALE_MAX) {
399             /* measure the amplitude of the function. Make sure that
400                graph boundaries are slightly higher than max vals
401                so we can see amplitude on the graph */
402               adj = (im->maxval - im->minval) * 0.1;
403               im->maxval += adj;
404         }
405         else {
406             scaled_min = im->minval / im->magfact;
407             scaled_max = im->maxval / im->magfact;
408             
409             for (i=1; sensiblevalues[i] > 0; i++){
410                 if (sensiblevalues[i-1]>=scaled_min &&
411                     sensiblevalues[i]<=scaled_min)      
412                     im->minval = sensiblevalues[i]*(im->magfact);
413                 
414                 if (-sensiblevalues[i-1]<=scaled_min &&
415                 -sensiblevalues[i]>=scaled_min)
416                     im->minval = -sensiblevalues[i-1]*(im->magfact);
417                 
418                 if (sensiblevalues[i-1] >= scaled_max &&
419                     sensiblevalues[i] <= scaled_max)
420                     im->maxval = sensiblevalues[i-1]*(im->magfact);
421                 
422                 if (-sensiblevalues[i-1]<=scaled_max &&
423                     -sensiblevalues[i] >=scaled_max)
424                     im->maxval = -sensiblevalues[i]*(im->magfact);
425             }
426         }
427     } else {
428         /* adjust min and max to the grid definition if there is one */
429         im->minval = (double)im->ylabfact * im->ygridstep * 
430             floor(im->minval / ((double)im->ylabfact * im->ygridstep));
431         im->maxval = (double)im->ylabfact * im->ygridstep * 
432             ceil(im->maxval /( (double)im->ylabfact * im->ygridstep));
433     }
434     
435 #ifdef DEBUG
436     fprintf(stderr,"SCALED Min: %6.2f Max: %6.2f Factor: %6.2f\n",
437            im->minval,im->maxval,im->magfact);
438 #endif
439 }
440
441 void
442 apply_gridfit(image_desc_t *im)
443 {
444   if (isnan(im->minval) || isnan(im->maxval))
445     return;
446   ytr(im,DNAN);
447   if (im->logarithmic) {
448     double ya, yb, ypix, ypixfrac;
449     double log10_range = log10(im->maxval) - log10(im->minval);
450     ya = pow((double)10, floor(log10(im->minval)));
451     while (ya < im->minval)
452       ya *= 10;
453     if (ya > im->maxval)
454       return; /* don't have y=10^x gridline */
455     yb = ya * 10;
456     if (yb <= im->maxval) {
457       /* we have at least 2 y=10^x gridlines.
458          Make sure distance between them in pixels
459          are an integer by expanding im->maxval */
460       double y_pixel_delta = ytr(im, ya) - ytr(im, yb);
461       double factor = y_pixel_delta / floor(y_pixel_delta);
462       double new_log10_range = factor * log10_range;
463       double new_ymax_log10 = log10(im->minval) + new_log10_range;
464       im->maxval = pow(10, new_ymax_log10);
465       ytr(im, DNAN); /* reset precalc */
466       log10_range = log10(im->maxval) - log10(im->minval);
467     }
468     /* make sure first y=10^x gridline is located on 
469        integer pixel position by moving scale slightly 
470        downwards (sub-pixel movement) */
471     ypix = ytr(im, ya) + im->ysize; /* add im->ysize so it always is positive */
472     ypixfrac = ypix - floor(ypix);
473     if (ypixfrac > 0 && ypixfrac < 1) {
474       double yfrac = ypixfrac / im->ysize;
475       im->minval = pow(10, log10(im->minval) - yfrac * log10_range);
476       im->maxval = pow(10, log10(im->maxval) - yfrac * log10_range);
477       ytr(im, DNAN); /* reset precalc */
478     }
479   } else {
480     /* Make sure we have an integer pixel distance between
481        each minor gridline */
482     double ypos1 = ytr(im, im->minval);
483     double ypos2 = ytr(im, im->minval + im->ygrid_scale.gridstep);
484     double y_pixel_delta = ypos1 - ypos2;
485     double factor = y_pixel_delta / floor(y_pixel_delta);
486     double new_range = factor * (im->maxval - im->minval);
487     double gridstep = im->ygrid_scale.gridstep;
488     double minor_y, minor_y_px, minor_y_px_frac;
489     im->maxval = im->minval + new_range;
490     ytr(im, DNAN); /* reset precalc */
491     /* make sure first minor gridline is on integer pixel y coord */
492     minor_y = gridstep * floor(im->minval / gridstep);
493     while (minor_y < im->minval)
494       minor_y += gridstep;
495     minor_y_px = ytr(im, minor_y) + im->ysize; /* ensure > 0 by adding ysize */
496     minor_y_px_frac = minor_y_px - floor(minor_y_px);
497     if (minor_y_px_frac > 0 && minor_y_px_frac < 1) {
498       double yfrac = minor_y_px_frac / im->ysize;
499       double range = im->maxval - im->minval;
500       im->minval = im->minval - yfrac * range;
501       im->maxval = im->maxval - yfrac * range;
502       ytr(im, DNAN); /* reset precalc */
503     }
504     calc_horizontal_grid(im); /* recalc with changed im->maxval */
505   }
506 }
507
508 /* reduce data reimplementation by Alex */
509
510 void
511 reduce_data(
512     enum cf_en     cf,         /* which consolidation function ?*/
513     unsigned long  cur_step,   /* step the data currently is in */
514     time_t         *start,     /* start, end and step as requested ... */
515     time_t         *end,       /* ... by the application will be   ... */
516     unsigned long  *step,      /* ... adjusted to represent reality    */
517     unsigned long  *ds_cnt,    /* number of data sources in file */
518     rrd_value_t    **data)     /* two dimensional array containing the data */
519 {
520     int i,reduce_factor = ceil((double)(*step) / (double)cur_step);
521     unsigned long col,dst_row,row_cnt,start_offset,end_offset,skiprows=0;
522     rrd_value_t    *srcptr,*dstptr;
523
524     (*step) = cur_step*reduce_factor; /* set new step size for reduced data */
525     dstptr = *data;
526     srcptr = *data;
527     row_cnt = ((*end)-(*start))/cur_step;
528
529 #ifdef DEBUG
530 #define DEBUG_REDUCE
531 #endif
532 #ifdef DEBUG_REDUCE
533 printf("Reducing %lu rows with factor %i time %lu to %lu, step %lu\n",
534                         row_cnt,reduce_factor,*start,*end,cur_step);
535 for (col=0;col<row_cnt;col++) {
536     printf("time %10lu: ",*start+(col+1)*cur_step);
537     for (i=0;i<*ds_cnt;i++)
538         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
539     printf("\n");
540 }
541 #endif
542
543     /* We have to combine [reduce_factor] rows of the source
544     ** into one row for the destination.  Doing this we also
545     ** need to take care to combine the correct rows.  First
546     ** alter the start and end time so that they are multiples
547     ** of the new step time.  We cannot reduce the amount of
548     ** time so we have to move the end towards the future and
549     ** the start towards the past.
550     */
551     end_offset = (*end) % (*step);
552     start_offset = (*start) % (*step);
553
554     /* If there is a start offset (which cannot be more than
555     ** one destination row), skip the appropriate number of
556     ** source rows and one destination row.  The appropriate
557     ** number is what we do know (start_offset/cur_step) of
558     ** the new interval (*step/cur_step aka reduce_factor).
559     */
560 #ifdef DEBUG_REDUCE
561 printf("start_offset: %lu  end_offset: %lu\n",start_offset,end_offset);
562 printf("row_cnt before:  %lu\n",row_cnt);
563 #endif
564     if (start_offset) {
565         (*start) = (*start)-start_offset;
566         skiprows=reduce_factor-start_offset/cur_step;
567         srcptr+=skiprows* *ds_cnt;
568         for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
569         row_cnt-=skiprows;
570     }
571 #ifdef DEBUG_REDUCE
572 printf("row_cnt between: %lu\n",row_cnt);
573 #endif
574
575     /* At the end we have some rows that are not going to be
576     ** used, the amount is end_offset/cur_step
577     */
578     if (end_offset) {
579         (*end) = (*end)-end_offset+(*step);
580         skiprows = end_offset/cur_step;
581         row_cnt-=skiprows;
582     }
583 #ifdef DEBUG_REDUCE
584 printf("row_cnt after:   %lu\n",row_cnt);
585 #endif
586
587 /* Sanity check: row_cnt should be multiple of reduce_factor */
588 /* if this gets triggered, something is REALLY WRONG ... we die immediately */
589
590     if (row_cnt%reduce_factor) {
591         printf("SANITY CHECK: %lu rows cannot be reduced by %i \n",
592                                 row_cnt,reduce_factor);
593         printf("BUG in reduce_data()\n");
594         exit(1);
595     }
596
597     /* Now combine reduce_factor intervals at a time
598     ** into one interval for the destination.
599     */
600
601     for (dst_row=0;row_cnt>=reduce_factor;dst_row++) {
602         for (col=0;col<(*ds_cnt);col++) {
603             rrd_value_t newval=DNAN;
604             unsigned long validval=0;
605
606             for (i=0;i<reduce_factor;i++) {
607                 if (isnan(srcptr[i*(*ds_cnt)+col])) {
608                     continue;
609                 }
610                 validval++;
611                 if (isnan(newval)) newval = srcptr[i*(*ds_cnt)+col];
612                 else {
613                     switch (cf) {
614                         case CF_HWPREDICT:
615                         case CF_DEVSEASONAL:
616                         case CF_DEVPREDICT:
617                         case CF_SEASONAL:
618                         case CF_AVERAGE:
619                             newval += srcptr[i*(*ds_cnt)+col];
620                             break;
621                         case CF_MINIMUM:
622                             newval = min (newval,srcptr[i*(*ds_cnt)+col]);
623                             break;
624                         case CF_FAILURES: 
625                         /* an interval contains a failure if any subintervals contained a failure */
626                         case CF_MAXIMUM:
627                             newval = max (newval,srcptr[i*(*ds_cnt)+col]);
628                             break;
629                         case CF_LAST:
630                             newval = srcptr[i*(*ds_cnt)+col];
631                             break;
632                     }
633                 }
634             }
635             if (validval == 0){newval = DNAN;} else{
636                 switch (cf) {
637                     case CF_HWPREDICT:
638             case CF_DEVSEASONAL:
639                     case CF_DEVPREDICT:
640                     case CF_SEASONAL:
641                     case CF_AVERAGE:                
642                        newval /= validval;
643                         break;
644                     case CF_MINIMUM:
645                     case CF_FAILURES:
646                     case CF_MAXIMUM:
647                     case CF_LAST:
648                         break;
649                 }
650             }
651             *dstptr++=newval;
652         }
653         srcptr+=(*ds_cnt)*reduce_factor;
654         row_cnt-=reduce_factor;
655     }
656     /* If we had to alter the endtime, we didn't have enough
657     ** source rows to fill the last row. Fill it with NaN.
658     */
659     if (end_offset) for (col=0;col<(*ds_cnt);col++) *dstptr++ = DNAN;
660 #ifdef DEBUG_REDUCE
661     row_cnt = ((*end)-(*start))/ *step;
662     srcptr = *data;
663     printf("Done reducing. Currently %lu rows, time %lu to %lu, step %lu\n",
664                                 row_cnt,*start,*end,*step);
665 for (col=0;col<row_cnt;col++) {
666     printf("time %10lu: ",*start+(col+1)*(*step));
667     for (i=0;i<*ds_cnt;i++)
668         printf(" %8.2e",srcptr[*ds_cnt*col+i]);
669     printf("\n");
670 }
671 #endif
672 }
673
674
675 /* get the data required for the graphs from the 
676    relevant rrds ... */
677
678 int
679 data_fetch( image_desc_t *im )
680 {
681     int       i,ii;
682     int skip;
683     /* pull the data from the log files ... */
684     for (i=0;i<im->gdes_c;i++){
685         /* only GF_DEF elements fetch data */
686         if (im->gdes[i].gf != GF_DEF) 
687             continue;
688
689         skip=0;
690         /* do we have it already ?*/
691         for (ii=0;ii<i;ii++){
692             if (im->gdes[ii].gf != GF_DEF) 
693                 continue;
694             if((strcmp(im->gdes[i].rrd,im->gdes[ii].rrd) == 0)
695                 && (im->gdes[i].cf == im->gdes[ii].cf)){
696                 /* OK the data it is here already ... 
697                  * we just copy the header portion */
698                 im->gdes[i].start = im->gdes[ii].start;
699                 im->gdes[i].end = im->gdes[ii].end;
700                 im->gdes[i].step = im->gdes[ii].step;
701                 im->gdes[i].ds_cnt = im->gdes[ii].ds_cnt;
702                 im->gdes[i].ds_namv = im->gdes[ii].ds_namv;             
703                 im->gdes[i].data = im->gdes[ii].data;
704                 im->gdes[i].data_first = 0;
705                 skip=1;
706             }
707             if (skip) 
708                 break;
709         }
710         if (! skip) {
711             unsigned long  ft_step = im->gdes[i].step ;
712             
713             if((rrd_fetch_fn(im->gdes[i].rrd,
714                              im->gdes[i].cf,
715                              &im->gdes[i].start,
716                              &im->gdes[i].end,
717                              &ft_step,
718                              &im->gdes[i].ds_cnt,
719                              &im->gdes[i].ds_namv,
720                              &im->gdes[i].data)) == -1){                
721                 return -1;
722             }
723             im->gdes[i].data_first = 1;     
724         
725             if (ft_step < im->gdes[i].step) {
726                 reduce_data(im->gdes[i].cf,
727                             ft_step,
728                             &im->gdes[i].start,
729                             &im->gdes[i].end,
730                             &im->gdes[i].step,
731                             &im->gdes[i].ds_cnt,
732                             &im->gdes[i].data);
733             } else {
734                 im->gdes[i].step = ft_step;
735             }
736         }
737         
738         /* lets see if the required data source is realy there */
739         for(ii=0;ii<im->gdes[i].ds_cnt;ii++){
740             if(strcmp(im->gdes[i].ds_namv[ii],im->gdes[i].ds_nam) == 0){
741                 im->gdes[i].ds=ii; }
742         }
743         if (im->gdes[i].ds== -1){
744             rrd_set_error("No DS called '%s' in '%s'",
745                           im->gdes[i].ds_nam,im->gdes[i].rrd);
746             return -1; 
747         }
748         
749     }
750     return 0;
751 }
752
753 /* evaluate the expressions in the CDEF functions */
754
755 /*************************************************************
756  * CDEF stuff 
757  *************************************************************/
758
759 long
760 find_var_wrapper(void *arg1, char *key)
761 {
762    return find_var((image_desc_t *) arg1, key);
763 }
764
765 /* find gdes containing var*/
766 long
767 find_var(image_desc_t *im, char *key){
768     long ii;
769     for(ii=0;ii<im->gdes_c-1;ii++){
770         if((im->gdes[ii].gf == GF_DEF 
771             || im->gdes[ii].gf == GF_VDEF
772             || im->gdes[ii].gf == GF_CDEF) 
773            && (strcmp(im->gdes[ii].vname,key) == 0)){
774             return ii; 
775         }          
776     }               
777     return -1;
778 }
779
780 /* find the largest common denominator for all the numbers
781    in the 0 terminated num array */
782 long
783 lcd(long *num){
784     long rest;
785     int i;
786     for (i=0;num[i+1]!=0;i++){
787         do { 
788             rest=num[i] % num[i+1];
789             num[i]=num[i+1]; num[i+1]=rest;
790         } while (rest!=0);
791         num[i+1] = num[i];
792     }
793 /*    return i==0?num[i]:num[i-1]; */
794       return num[i];
795 }
796
797 /* run the rpn calculator on all the VDEF and CDEF arguments */
798 int
799 data_calc( image_desc_t *im){
800
801     int       gdi;
802     int       dataidx;
803     long      *steparray, rpi;
804     int       stepcnt;
805     time_t    now;
806     rpnstack_t rpnstack;
807
808     rpnstack_init(&rpnstack);
809
810     for (gdi=0;gdi<im->gdes_c;gdi++){
811         /* Look for GF_VDEF and GF_CDEF in the same loop,
812          * so CDEFs can use VDEFs and vice versa
813          */
814         switch (im->gdes[gdi].gf) {
815             case GF_VDEF:
816                 /* A VDEF has no DS.  This also signals other parts
817                  * of rrdtool that this is a VDEF value, not a CDEF.
818                  */
819                 im->gdes[gdi].ds_cnt = 0;
820                 if (vdef_calc(im,gdi)) {
821                     rrd_set_error("Error processing VDEF '%s'"
822                         ,im->gdes[gdi].vname
823                         );
824                     rpnstack_free(&rpnstack);
825                     return -1;
826                 }
827                 break;
828             case GF_CDEF:
829                 im->gdes[gdi].ds_cnt = 1;
830                 im->gdes[gdi].ds = 0;
831                 im->gdes[gdi].data_first = 1;
832                 im->gdes[gdi].start = 0;
833                 im->gdes[gdi].end = 0;
834                 steparray=NULL;
835                 stepcnt = 0;
836                 dataidx=-1;
837
838                 /* Find the variables in the expression.
839                  * - VDEF variables are substituted by their values
840                  *   and the opcode is changed into OP_NUMBER.
841                  * - CDEF variables are analized for their step size,
842                  *   the lowest common denominator of all the step
843                  *   sizes of the data sources involved is calculated
844                  *   and the resulting number is the step size for the
845                  *   resulting data source.
846                  */
847                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
848                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
849                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
850                         if (im->gdes[ptr].ds_cnt == 0) {
851 #if 0
852 printf("DEBUG: inside CDEF '%s' processing VDEF '%s'\n",
853         im->gdes[gdi].vname,
854         im->gdes[ptr].vname);
855 printf("DEBUG: value from vdef is %f\n",im->gdes[ptr].vf.val);
856 #endif
857                             im->gdes[gdi].rpnp[rpi].val = im->gdes[ptr].vf.val;
858                             im->gdes[gdi].rpnp[rpi].op  = OP_NUMBER;
859                         } else {
860                             if ((steparray = rrd_realloc(steparray, (++stepcnt+1)*sizeof(*steparray)))==NULL){
861                                 rrd_set_error("realloc steparray");
862                                 rpnstack_free(&rpnstack);
863                                 return -1;
864                             };
865
866                             steparray[stepcnt-1] = im->gdes[ptr].step;
867
868                             /* adjust start and end of cdef (gdi) so
869                              * that it runs from the latest start point
870                              * to the earliest endpoint of any of the
871                              * rras involved (ptr)
872                              */
873                             if(im->gdes[gdi].start < im->gdes[ptr].start)
874                                 im->gdes[gdi].start = im->gdes[ptr].start;
875
876                             if(im->gdes[gdi].end == 0 ||
877                                         im->gdes[gdi].end > im->gdes[ptr].end)
878                                 im->gdes[gdi].end = im->gdes[ptr].end;
879                 
880                             /* store pointer to the first element of
881                              * the rra providing data for variable,
882                              * further save step size and data source
883                              * count of this rra
884                              */ 
885                             im->gdes[gdi].rpnp[rpi].data =  im->gdes[ptr].data + im->gdes[ptr].ds;
886                             im->gdes[gdi].rpnp[rpi].step = im->gdes[ptr].step;
887                             im->gdes[gdi].rpnp[rpi].ds_cnt = im->gdes[ptr].ds_cnt;
888
889                             /* backoff the *.data ptr; this is done so
890                              * rpncalc() function doesn't have to treat
891                              * the first case differently
892                              */
893                         } /* if ds_cnt != 0 */
894                     } /* if OP_VARIABLE */
895                 } /* loop through all rpi */
896
897                 /* move the data pointers to the correct period */
898                 for(rpi=0;im->gdes[gdi].rpnp[rpi].op != OP_END;rpi++){
899                     if(im->gdes[gdi].rpnp[rpi].op == OP_VARIABLE){
900                         long ptr = im->gdes[gdi].rpnp[rpi].ptr;
901                         if(im->gdes[gdi].start > im->gdes[ptr].start) {
902                             im->gdes[gdi].rpnp[rpi].data += im->gdes[gdi].rpnp[rpi].ds_cnt;
903                         }
904                      }
905                 }
906         
907
908                 if(steparray == NULL){
909                     rrd_set_error("rpn expressions without DEF"
910                                 " or CDEF variables are not supported");
911                     rpnstack_free(&rpnstack);
912                     return -1;    
913                 }
914                 steparray[stepcnt]=0;
915                 /* Now find the resulting step.  All steps in all
916                  * used RRAs have to be visited
917                  */
918                 im->gdes[gdi].step = lcd(steparray);
919                 free(steparray);
920                 if((im->gdes[gdi].data = malloc((
921                                 (im->gdes[gdi].end-im->gdes[gdi].start) 
922                                     / im->gdes[gdi].step)
923                                     * sizeof(double)))==NULL){
924                     rrd_set_error("malloc im->gdes[gdi].data");
925                     rpnstack_free(&rpnstack);
926                     return -1;
927                 }
928         
929                 /* Step through the new cdef results array and
930                  * calculate the values
931                  */
932                 for (now = im->gdes[gdi].start + im->gdes[gdi].step;
933                                 now<=im->gdes[gdi].end;
934                                 now += im->gdes[gdi].step)
935                 {
936                     rpnp_t  *rpnp = im -> gdes[gdi].rpnp;
937
938                     /* 3rd arg of rpn_calc is for OP_VARIABLE lookups;
939                      * in this case we are advancing by timesteps;
940                      * we use the fact that time_t is a synonym for long
941                      */
942                     if (rpn_calc(rpnp,&rpnstack,(long) now, 
943                                 im->gdes[gdi].data,++dataidx) == -1) {
944                         /* rpn_calc sets the error string */
945                         rpnstack_free(&rpnstack); 
946                         return -1;
947                     } 
948                 } /* enumerate over time steps within a CDEF */
949                 break;
950             default:
951                 continue;
952         }
953     } /* enumerate over CDEFs */
954     rpnstack_free(&rpnstack);
955     return 0;
956 }
957
958 /* massage data so, that we get one value for each x coordinate in the graph */
959 int
960 data_proc( image_desc_t *im ){
961     long i,ii;
962     double pixstep = (double)(im->end-im->start)
963         /(double)im->xsize; /* how much time 
964                                passes in one pixel */
965     double paintval;
966     double minval=DNAN,maxval=DNAN;
967     
968     unsigned long gr_time;    
969
970     /* memory for the processed data */
971     for(i=0;i<im->gdes_c;i++){
972       if((im->gdes[i].gf==GF_LINE) ||
973          (im->gdes[i].gf==GF_AREA) ||
974          (im->gdes[i].gf==GF_TICK) ||
975          (im->gdes[i].gf==GF_STACK)){
976         if((im->gdes[i].p_data = malloc((im->xsize +1)
977                                         * sizeof(rrd_value_t)))==NULL){
978           rrd_set_error("malloc data_proc");
979           return -1;
980         }
981       }
982     }
983     
984     for(i=0;i<im->xsize;i++){
985         long vidx;
986         gr_time = im->start+pixstep*i; /* time of the 
987                                           current step */
988         paintval=0.0;
989         
990         for(ii=0;ii<im->gdes_c;ii++){
991           double value;
992             switch(im->gdes[ii].gf){
993             case GF_LINE:
994             case GF_AREA:
995                 case GF_TICK:
996                 paintval = 0.0;
997             case GF_STACK:
998                 vidx = im->gdes[ii].vidx;
999
1000                 value =
1001                     im->gdes[vidx].data[
1002                         ((unsigned long)floor(
1003         (double)(gr_time-im->gdes[vidx].start) / im->gdes[vidx].step
1004                                              )
1005                         )       *im->gdes[vidx].ds_cnt
1006                                 +im->gdes[vidx].ds];
1007
1008                 if (! isnan(value)) {
1009                   paintval += value;
1010                   im->gdes[ii].p_data[i] = paintval;
1011                   /* GF_TICK: the data values are not relevant for min and max */
1012                   if (finite(paintval) && im->gdes[ii].gf != GF_TICK ){
1013                    if (isnan(minval) || paintval <  minval)
1014                      minval = paintval;
1015                    if (isnan(maxval) || paintval >  maxval)
1016                      maxval = paintval;
1017                   }
1018                 } else {
1019                   im->gdes[ii].p_data[i] = DNAN;
1020                 }
1021                 break;
1022             case GF_PRINT:
1023             case GF_GPRINT:
1024             case GF_COMMENT:
1025             case GF_HRULE:
1026             case GF_VRULE:
1027             case GF_DEF:               
1028             case GF_CDEF:
1029             case GF_VDEF:
1030             case GF_PART:
1031                 break;
1032             }
1033         }
1034     }
1035
1036     /* if min or max have not been asigned a value this is because
1037        there was no data in the graph ... this is not good ...
1038        lets set these to dummy values then ... */
1039
1040     if (isnan(minval)) minval = 0.0;
1041     if (isnan(maxval)) maxval = 1.0;
1042     
1043     /* adjust min and max values */
1044     if (isnan(im->minval) 
1045         || ((!im->logarithmic && !im->rigid) /* don't adjust low-end with log scale */
1046             && im->minval > minval))
1047         im->minval = minval;
1048     if (isnan(im->maxval) 
1049         || (!im->rigid 
1050             && im->maxval < maxval)){
1051         if (im->logarithmic)
1052             im->maxval = maxval * 1.1;
1053         else
1054             im->maxval = maxval;
1055     }
1056     /* make sure min and max are not equal */
1057     if (im->minval == im->maxval) {
1058       im->maxval *= 1.01; 
1059       if (! im->logarithmic) {
1060         im->minval *= 0.99;
1061       }
1062       
1063       /* make sure min and max are not both zero */
1064       if (im->maxval == 0.0) {
1065             im->maxval = 1.0;
1066       }
1067         
1068     }
1069     return 0;
1070 }
1071
1072
1073
1074 /* identify the point where the first gridline, label ... gets placed */
1075
1076 time_t
1077 find_first_time(
1078     time_t   start, /* what is the initial time */
1079     enum tmt_en baseint,  /* what is the basic interval */
1080     long     basestep /* how many if these do we jump a time */
1081     )
1082 {
1083     struct tm tm;
1084     tm = *localtime(&start);
1085     switch(baseint){
1086     case TMT_SECOND:
1087         tm.tm_sec -= tm.tm_sec % basestep; break;
1088     case TMT_MINUTE: 
1089         tm.tm_sec=0;
1090         tm.tm_min -= tm.tm_min % basestep; 
1091         break;
1092     case TMT_HOUR:
1093         tm.tm_sec=0;
1094         tm.tm_min = 0;
1095         tm.tm_hour -= tm.tm_hour % basestep; break;
1096     case TMT_DAY:
1097         /* we do NOT look at the basestep for this ... */
1098         tm.tm_sec=0;
1099         tm.tm_min = 0;
1100         tm.tm_hour = 0; break;
1101     case TMT_WEEK:
1102         /* we do NOT look at the basestep for this ... */
1103         tm.tm_sec=0;
1104         tm.tm_min = 0;
1105         tm.tm_hour = 0;
1106         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1107         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1108         break;
1109     case TMT_MONTH:
1110         tm.tm_sec=0;
1111         tm.tm_min = 0;
1112         tm.tm_hour = 0;
1113         tm.tm_mday = 1;
1114         tm.tm_mon -= tm.tm_mon % basestep; break;
1115
1116     case TMT_YEAR:
1117         tm.tm_sec=0;
1118         tm.tm_min = 0;
1119         tm.tm_hour = 0;
1120         tm.tm_mday = 1;
1121         tm.tm_mon = 0;
1122         tm.tm_year -= (tm.tm_year+1900) % basestep;
1123         
1124     }
1125     return mktime(&tm);
1126 }
1127 /* identify the point where the next gridline, label ... gets placed */
1128 time_t 
1129 find_next_time(
1130     time_t   current, /* what is the initial time */
1131     enum tmt_en baseint,  /* what is the basic interval */
1132     long     basestep /* how many if these do we jump a time */
1133     )
1134 {
1135     struct tm tm;
1136     time_t madetime;
1137     tm = *localtime(&current);
1138     do {
1139         switch(baseint){
1140         case TMT_SECOND:
1141             tm.tm_sec += basestep; break;
1142         case TMT_MINUTE: 
1143             tm.tm_min += basestep; break;
1144         case TMT_HOUR:
1145             tm.tm_hour += basestep; break;
1146         case TMT_DAY:
1147             tm.tm_mday += basestep; break;
1148         case TMT_WEEK:
1149             tm.tm_mday += 7*basestep; break;
1150         case TMT_MONTH:
1151             tm.tm_mon += basestep; break;
1152         case TMT_YEAR:
1153             tm.tm_year += basestep;     
1154         }
1155         madetime = mktime(&tm);
1156     } while (madetime == -1); /* this is necessary to skip impssible times
1157                                  like the daylight saving time skips */
1158     return madetime;
1159           
1160 }
1161
1162
1163 /* calculate values required for PRINT and GPRINT functions */
1164
1165 int
1166 print_calc(image_desc_t *im, char ***prdata) 
1167 {
1168     long i,ii,validsteps;
1169     double printval;
1170     time_t printtime;
1171     int graphelement = 0;
1172     long vidx;
1173     int max_ii; 
1174     double magfact = -1;
1175     char *si_symb = "";
1176     char *percent_s;
1177     int prlines = 1;
1178     if (im->imginfo) prlines++;
1179     for(i=0;i<im->gdes_c;i++){
1180         switch(im->gdes[i].gf){
1181         case GF_PRINT:
1182             prlines++;
1183             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1184                 rrd_set_error("realloc prdata");
1185                 return 0;
1186             }
1187         case GF_GPRINT:
1188             /* PRINT and GPRINT can now print VDEF generated values.
1189              * There's no need to do any calculations on them as these
1190              * calculations were already made.
1191              */
1192             vidx = im->gdes[i].vidx;
1193             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1194                 printval = im->gdes[vidx].vf.val;
1195                 printtime = im->gdes[vidx].vf.when;
1196             } else { /* need to calculate max,min,avg etcetera */
1197                 max_ii =((im->gdes[vidx].end 
1198                         - im->gdes[vidx].start)
1199                         / im->gdes[vidx].step
1200                         * im->gdes[vidx].ds_cnt);
1201                 printval = DNAN;
1202                 validsteps = 0;
1203                 for(    ii=im->gdes[vidx].ds;
1204                         ii < max_ii;
1205                         ii+=im->gdes[vidx].ds_cnt){
1206                     if (! finite(im->gdes[vidx].data[ii]))
1207                         continue;
1208                     if (isnan(printval)){
1209                         printval = im->gdes[vidx].data[ii];
1210                         validsteps++;
1211                         continue;
1212                     }
1213
1214                     switch (im->gdes[i].cf){
1215                         case CF_HWPREDICT:
1216                         case CF_DEVPREDICT:
1217                         case CF_DEVSEASONAL:
1218                         case CF_SEASONAL:
1219                         case CF_AVERAGE:
1220                             validsteps++;
1221                             printval += im->gdes[vidx].data[ii];
1222                             break;
1223                         case CF_MINIMUM:
1224                             printval = min( printval, im->gdes[vidx].data[ii]);
1225                             break;
1226                         case CF_FAILURES:
1227                         case CF_MAXIMUM:
1228                             printval = max( printval, im->gdes[vidx].data[ii]);
1229                             break;
1230                         case CF_LAST:
1231                             printval = im->gdes[vidx].data[ii];
1232                     }
1233                 }
1234                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1235                     if (validsteps > 1) {
1236                         printval = (printval / validsteps);
1237                     }
1238                 }
1239             } /* prepare printval */
1240
1241             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1242                 if (im->gdes[i].gf == GF_PRINT){
1243                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1244                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1245                                         ctime(&printtime),printtime);
1246                     (*prdata)[prlines-1] = NULL;
1247                 } else {
1248                     sprintf(im->gdes[i].legend,"%s (%lu)",
1249                                         ctime(&printtime),printtime);
1250                     graphelement = 1;
1251                 }
1252             } else {
1253             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1254                 /* Magfact is set to -1 upon entry to print_calc.  If it
1255                  * is still less than 0, then we need to run auto_scale.
1256                  * Otherwise, put the value into the correct units.  If
1257                  * the value is 0, then do not set the symbol or magnification
1258                  * so next the calculation will be performed again. */
1259                 if (magfact < 0.0) {
1260                     auto_scale(im,&printval,&si_symb,&magfact);
1261                     if (printval == 0.0)
1262                         magfact = -1.0;
1263                 } else {
1264                     printval /= magfact;
1265                 }
1266                 *(++percent_s) = 's';
1267             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1268                 auto_scale(im,&printval,&si_symb,&magfact);
1269             }
1270
1271             if (im->gdes[i].gf == GF_PRINT){
1272                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1273                 if (bad_format(im->gdes[i].format)) {
1274                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1275                         return -1;
1276                 }
1277 #ifdef HAVE_SNPRINTF
1278                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1279 #else
1280                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1281 #endif
1282                 (*prdata)[prlines-1] = NULL;
1283             } else {
1284                 /* GF_GPRINT */
1285
1286                 if (bad_format(im->gdes[i].format)) {
1287                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1288                         return -1;
1289                 }
1290 #ifdef HAVE_SNPRINTF
1291                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1292 #else
1293                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1294 #endif
1295                 graphelement = 1;
1296             }
1297             }
1298             break;
1299         case GF_LINE:
1300         case GF_AREA:
1301         case GF_TICK:
1302         case GF_STACK:
1303         case GF_HRULE:
1304         case GF_VRULE:
1305             graphelement = 1;
1306             break;
1307         case GF_COMMENT:
1308         case GF_DEF:
1309         case GF_CDEF:       
1310         case GF_VDEF:       
1311         case GF_PART:
1312             break;
1313         }
1314     }
1315     return graphelement;
1316 }
1317
1318
1319 /* place legends with color spots */
1320 int
1321 leg_place(image_desc_t *im)
1322 {
1323     /* graph labels */
1324     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1325     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1326     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1327     int   fill=0, fill_last;
1328     int   leg_c = 0;
1329     int   leg_x = border, leg_y = im->yimg;
1330     int   leg_cc;
1331     int   glue = 0;
1332     int   i,ii, mark = 0;
1333     char  prt_fctn; /*special printfunctions */
1334     int  *legspace;
1335
1336   if( !(im->extra_flags & NOLEGEND) ) {
1337     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1338        rrd_set_error("malloc for legspace");
1339        return -1;
1340     }
1341
1342     for(i=0;i<im->gdes_c;i++){
1343         fill_last = fill;
1344
1345         leg_cc = strlen(im->gdes[i].legend);
1346         
1347         /* is there a controle code ant the end of the legend string ? */ 
1348         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1349             prt_fctn = im->gdes[i].legend[leg_cc-1];
1350             leg_cc -= 2;
1351             im->gdes[i].legend[leg_cc] = '\0';
1352         } else {
1353             prt_fctn = '\0';
1354         }
1355         /* remove exess space */
1356         while (prt_fctn=='g' && 
1357                leg_cc > 0 && 
1358                im->gdes[i].legend[leg_cc-1]==' '){
1359            leg_cc--;
1360            im->gdes[i].legend[leg_cc]='\0';
1361         }
1362         if (leg_cc != 0 ){
1363            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1364            
1365            if (fill > 0){ 
1366                /* no interleg space if string ends in \g */
1367                fill += legspace[i];
1368             }
1369             if (im->gdes[i].gf != GF_GPRINT && 
1370                 im->gdes[i].gf != GF_COMMENT) { 
1371                 fill += box;       
1372             }
1373            fill += gfx_get_text_width(im->canvas, fill+border,
1374                                       im->text_prop[TEXT_PROP_LEGEND].font,
1375                                       im->text_prop[TEXT_PROP_LEGEND].size,
1376                                       im->tabwidth,
1377                                       im->gdes[i].legend);
1378             leg_c++;
1379         } else {
1380            legspace[i]=0;
1381         }
1382         /* who said there was a special tag ... ?*/
1383         if (prt_fctn=='g') {    
1384            prt_fctn = '\0';
1385         }
1386         if (prt_fctn == '\0') {
1387             if (i == im->gdes_c -1 ) prt_fctn ='l';
1388             
1389             /* is it time to place the legends ? */
1390             if (fill > im->ximg - 2*border){
1391                 if (leg_c > 1) {
1392                     /* go back one */
1393                     i--; 
1394                     fill = fill_last;
1395                     leg_c--;
1396                     prt_fctn = 'j';
1397                 } else {
1398                     prt_fctn = 'l';
1399                 }
1400                 
1401             }
1402         }
1403
1404
1405         if (prt_fctn != '\0'){
1406             leg_x = border;
1407             if (leg_c >= 2 && prt_fctn == 'j') {
1408                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1409             } else {
1410                 glue = 0;
1411             }
1412             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1413             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1414
1415             for(ii=mark;ii<=i;ii++){
1416                 if(im->gdes[ii].legend[0]=='\0')
1417                     continue;
1418                 im->gdes[ii].leg_x = leg_x;
1419                 im->gdes[ii].leg_y = leg_y;
1420                 leg_x += 
1421                  gfx_get_text_width(im->canvas, leg_x,
1422                                       im->text_prop[TEXT_PROP_LEGEND].font,
1423                                       im->text_prop[TEXT_PROP_LEGEND].size,
1424                                       im->tabwidth,
1425                                       im->gdes[ii].legend) 
1426                    + legspace[ii]
1427                    + glue;
1428                 if (im->gdes[ii].gf != GF_GPRINT && 
1429                     im->gdes[ii].gf != GF_COMMENT) 
1430                     leg_x += box;          
1431             }       
1432             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1433             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1434             fill = 0;
1435             leg_c = 0;
1436             mark = ii;
1437         }          
1438     }
1439     im->yimg = leg_y;
1440     free(legspace);
1441   }
1442   return 0;
1443 }
1444
1445 /* create a grid on the graph. it determines what to do
1446    from the values of xsize, start and end */
1447
1448 /* the xaxis labels are determined from the number of seconds per pixel
1449    in the requested graph */
1450
1451
1452
1453 int
1454 calc_horizontal_grid(image_desc_t   *im)
1455 {
1456     double   range;
1457     double   scaledrange;
1458     int      pixel,i;
1459     int      gridind;
1460     int      decimals, fractionals;
1461
1462     im->ygrid_scale.labfact=2;
1463     gridind=-1;
1464     range =  im->maxval - im->minval;
1465     scaledrange = range / im->magfact;
1466
1467         /* does the scale of this graph make it impossible to put lines
1468            on it? If so, give up. */
1469         if (isnan(scaledrange)) {
1470                 return 0;
1471         }
1472
1473     /* find grid spaceing */
1474     pixel=1;
1475     if(isnan(im->ygridstep)){
1476         if(im->extra_flags & ALTYGRID) {
1477             /* find the value with max number of digits. Get number of digits */
1478             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1479             if(decimals <= 0) /* everything is small. make place for zero */
1480                 decimals = 1;
1481             
1482             fractionals = floor(log10(range));
1483             if(fractionals < 0) /* small amplitude. */
1484                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1485             else
1486                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1487             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1488             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1489                 im->ygrid_scale.gridstep = 0.1;
1490             /* should have at least 5 lines but no more then 15 */
1491             if(range/im->ygrid_scale.gridstep < 5)
1492                 im->ygrid_scale.gridstep /= 10;
1493             if(range/im->ygrid_scale.gridstep > 15)
1494                 im->ygrid_scale.gridstep *= 10;
1495             if(range/im->ygrid_scale.gridstep > 5) {
1496                 im->ygrid_scale.labfact = 1;
1497                 if(range/im->ygrid_scale.gridstep > 8)
1498                     im->ygrid_scale.labfact = 2;
1499             }
1500             else {
1501                 im->ygrid_scale.gridstep /= 5;
1502                 im->ygrid_scale.labfact = 5;
1503             }
1504         }
1505         else {
1506             for(i=0;ylab[i].grid > 0;i++){
1507                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1508                 if (gridind == -1 && pixel > 5) {
1509                     gridind = i;
1510                     break;
1511                 }
1512             }
1513             
1514             for(i=0; i<4;i++) {
1515                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1516                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1517                   break;
1518                }                          
1519             } 
1520             
1521             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1522         }
1523     } else {
1524         im->ygrid_scale.gridstep = im->ygridstep;
1525         im->ygrid_scale.labfact = im->ylabfact;
1526     }
1527     return 1;
1528 }
1529
1530 int draw_horizontal_grid(image_desc_t *im)
1531 {
1532     int      i;
1533     double   scaledstep;
1534     char     graph_label[100];
1535     double X0=im->xorigin;
1536     double X1=im->xorigin+im->xsize;
1537    
1538     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1539     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1540     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1541     for (i = sgrid; i <= egrid; i++){
1542        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1543        if ( Y0 >= im->yorigin-im->ysize
1544                  && Y0 <= im->yorigin){       
1545             if(i % im->ygrid_scale.labfact == 0){               
1546                 if (i==0 || im->symbol == ' ') {
1547                     if(scaledstep < 1){
1548                         if(im->extra_flags & ALTYGRID) {
1549                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1550                         }
1551                         else {
1552                             sprintf(graph_label,"%4.1f",scaledstep*i);
1553                         }
1554                     } else {
1555                         sprintf(graph_label,"%4.0f",scaledstep*i);
1556                     }
1557                 }else {
1558                     if(scaledstep < 1){
1559                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1560                     } else {
1561                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1562                     }
1563                 }
1564
1565                gfx_new_text ( im->canvas,
1566                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1567                               im->graph_col[GRC_FONT],
1568                               im->text_prop[TEXT_PROP_AXIS].font,
1569                               im->text_prop[TEXT_PROP_AXIS].size,
1570                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1571                               graph_label );
1572                gfx_new_line ( im->canvas,
1573                               X0-2,Y0,
1574                               X1+2,Y0,
1575                               MGRIDWIDTH, im->graph_col[GRC_MGRID] );          
1576                
1577             } else {            
1578                gfx_new_line ( im->canvas,
1579                               X0-1,Y0,
1580                               X1+1,Y0,
1581                               GRIDWIDTH, im->graph_col[GRC_GRID] );            
1582                
1583             }       
1584         }       
1585     } 
1586     return 1;
1587 }
1588
1589 /* logaritmic horizontal grid */
1590 int
1591 horizontal_log_grid(image_desc_t   *im)   
1592 {
1593     double   pixpex;
1594     int      ii,i;
1595     int      minoridx=0, majoridx=0;
1596     char     graph_label[100];
1597     double   X0,X1,Y0;   
1598     double   value, pixperstep, minstep;
1599
1600     /* find grid spaceing */
1601     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1602
1603         if (isnan(pixpex)) {
1604                 return 0;
1605         }
1606
1607     for(i=0;yloglab[i][0] > 0;i++){
1608         minstep = log10(yloglab[i][0]);
1609         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1610             if(yloglab[i][ii+2]==0){
1611                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1612                 break;
1613             }
1614         }
1615         pixperstep = pixpex * minstep;
1616         if(pixperstep > 5){minoridx = i;}
1617        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1618     }
1619    
1620    X0=im->xorigin;
1621    X1=im->xorigin+im->xsize;
1622     /* paint minor grid */
1623     for (value = pow((double)10, log10(im->minval) 
1624                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1625          value  <= im->maxval;
1626          value *= yloglab[minoridx][0]){
1627         if (value < im->minval) continue;
1628         i=0;    
1629         while(yloglab[minoridx][++i] > 0){          
1630            Y0 = ytr(im,value * yloglab[minoridx][i]);
1631            if (Y0 <= im->yorigin - im->ysize) break;
1632            gfx_new_line ( im->canvas,
1633                           X0-1,Y0,
1634                           X1+1,Y0,
1635                           GRIDWIDTH, im->graph_col[GRC_GRID] );
1636         }
1637     }
1638
1639     /* paint major grid and labels*/
1640     for (value = pow((double)10, log10(im->minval) 
1641                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1642          value <= im->maxval;
1643          value *= yloglab[majoridx][0]){
1644         if (value < im->minval) continue;
1645         i=0;    
1646         while(yloglab[majoridx][++i] > 0){          
1647            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1648            if (Y0 <= im->yorigin - im->ysize) break;
1649            gfx_new_line ( im->canvas,
1650                           X0-2,Y0,
1651                           X1+2,Y0,
1652                           MGRIDWIDTH, im->graph_col[GRC_MGRID] );
1653            
1654            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1655            gfx_new_text ( im->canvas,
1656                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1657                           im->graph_col[GRC_FONT],
1658                           im->text_prop[TEXT_PROP_AXIS].font,
1659                           im->text_prop[TEXT_PROP_AXIS].size,
1660                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1661                           graph_label );
1662         } 
1663     }
1664         return 1;
1665 }
1666
1667
1668 void
1669 vertical_grid(
1670     image_desc_t   *im )
1671 {   
1672     int xlab_sel;               /* which sort of label and grid ? */
1673     time_t ti, tilab;
1674     long factor;
1675     char graph_label[100];
1676     double X0,Y0,Y1; /* points for filled graph and more*/
1677    
1678
1679     /* the type of time grid is determined by finding
1680        the number of seconds per pixel in the graph */
1681     
1682     
1683     if(im->xlab_user.minsec == -1){
1684         factor=(im->end - im->start)/im->xsize;
1685         xlab_sel=0;
1686         while ( xlab[xlab_sel+1].minsec != -1 
1687                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1688         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1689         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1690         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1691         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1692         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1693         im->xlab_user.labst = xlab[xlab_sel].labst;
1694         im->xlab_user.precis = xlab[xlab_sel].precis;
1695         im->xlab_user.stst = xlab[xlab_sel].stst;
1696     }
1697     
1698     /* y coords are the same for every line ... */
1699     Y0 = im->yorigin;
1700     Y1 = im->yorigin-im->ysize;
1701    
1702
1703     /* paint the minor grid */
1704     for(ti = find_first_time(im->start,
1705                             im->xlab_user.gridtm,
1706                             im->xlab_user.gridst);
1707         ti < im->end; 
1708         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1709         ){
1710         /* are we inside the graph ? */
1711         if (ti < im->start || ti > im->end) continue;
1712        X0 = xtr(im,ti);       
1713        gfx_new_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH, im->graph_col[GRC_GRID]);
1714        
1715     }
1716
1717     /* paint the major grid */
1718     for(ti = find_first_time(im->start,
1719                             im->xlab_user.mgridtm,
1720                             im->xlab_user.mgridst);
1721         ti < im->end; 
1722         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1723         ){
1724         /* are we inside the graph ? */
1725         if (ti < im->start || ti > im->end) continue;
1726        X0 = xtr(im,ti);
1727        gfx_new_line(im->canvas,X0,Y0+2, X0,Y1-2,MGRIDWIDTH, im->graph_col[GRC_MGRID]);
1728        
1729     }
1730     /* paint the labels below the graph */
1731     for(ti = find_first_time(im->start,
1732                             im->xlab_user.labtm,
1733                             im->xlab_user.labst);
1734         ti <= im->end; 
1735         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1736         ){
1737         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1738         /* are we inside the graph ? */
1739         if (ti < im->start || ti > im->end) continue;
1740
1741 #if HAVE_STRFTIME
1742         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1743 #else
1744 # error "your libc has no strftime I guess we'll abort the exercise here."
1745 #endif
1746        gfx_new_text ( im->canvas,
1747                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1748                       im->graph_col[GRC_FONT],
1749                       im->text_prop[TEXT_PROP_AXIS].font,
1750                       im->text_prop[TEXT_PROP_AXIS].size,
1751                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1752                       graph_label );
1753        
1754     }
1755
1756 }
1757
1758
1759 void 
1760 axis_paint(
1761    image_desc_t   *im
1762            )
1763 {   
1764     /* draw x and y axis */
1765     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1766                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1767                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1768        
1769        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1770                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1771                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1772    
1773        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1774                          im->xorigin+im->xsize+4,im->yorigin,
1775                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1776    
1777        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1778                          im->xorigin,im->yorigin-im->ysize-4,
1779                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1780    
1781     
1782     /* arrow for X axis direction */
1783     gfx_new_area ( im->canvas, 
1784                    im->xorigin+im->xsize+3,  im->yorigin-3,
1785                    im->xorigin+im->xsize+3,  im->yorigin+4,
1786                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1787                    im->graph_col[GRC_ARROW]);
1788    
1789    
1790    
1791 }
1792
1793 void
1794 grid_paint(image_desc_t   *im)
1795 {   
1796     long i;
1797     int res=0;
1798     double X0,Y0; /* points for filled graph and more*/
1799     gfx_node_t *node;
1800
1801     /* draw 3d border */
1802     node = gfx_new_area (im->canvas, 0,im->yimg,
1803                                  2,im->yimg-2,
1804                                  2,2,im->graph_col[GRC_SHADEA]);
1805     gfx_add_point( node , im->ximg - 2, 2 );
1806     gfx_add_point( node , im->ximg, 0 );
1807     gfx_add_point( node , 0,0 );
1808 /*    gfx_add_point( node , 0,im->yimg ); */
1809    
1810     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1811                                   im->ximg-2,im->yimg-2,
1812                                   im->ximg - 2, 2,
1813                                  im->graph_col[GRC_SHADEB]);
1814     gfx_add_point( node ,   im->ximg,0);
1815     gfx_add_point( node ,   im->ximg,im->yimg);
1816     gfx_add_point( node ,   0,im->yimg);
1817 /*    gfx_add_point( node , 0,im->yimg ); */
1818    
1819    
1820     if (im->draw_x_grid == 1 )
1821       vertical_grid(im);
1822     
1823     if (im->draw_y_grid == 1){
1824         if(im->logarithmic){
1825                 res = horizontal_log_grid(im);
1826         } else {
1827                 res = draw_horizontal_grid(im);
1828         }
1829
1830         /* dont draw horizontal grid if there is no min and max val */
1831         if (! res ) {
1832           char *nodata = "No Data found";
1833            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1834                         im->graph_col[GRC_FONT],
1835                         im->text_prop[TEXT_PROP_AXIS].font,
1836                         im->text_prop[TEXT_PROP_AXIS].size,
1837                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1838                         nodata );          
1839         }
1840     }
1841
1842     /* yaxis description */
1843         if (im->canvas->imgformat != IF_PNG) {
1844             gfx_new_text( im->canvas,
1845                           7, (im->yorigin - im->ysize/2),
1846                           im->graph_col[GRC_FONT],
1847                           im->text_prop[TEXT_PROP_AXIS].font,
1848                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1849                           GFX_H_CENTER, GFX_V_CENTER,
1850                           im->ylegend);
1851         } else {
1852             /* horrible hack until we can actually print vertically */
1853             {
1854                 int n;
1855                 int l=strlen(im->ylegend);
1856                 char s[2];
1857                 for (n=0;n<strlen(im->ylegend);n++) {
1858                     s[0]=im->ylegend[n];
1859                     s[1]='\0';
1860                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1861                         im->graph_col[GRC_FONT],
1862                         im->text_prop[TEXT_PROP_AXIS].font,
1863                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1864                         GFX_H_CENTER, GFX_V_CENTER,
1865                         s);
1866                 }
1867             }
1868         }
1869    
1870     /* graph title */
1871     gfx_new_text( im->canvas,
1872                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1873                   im->graph_col[GRC_FONT],
1874                   im->text_prop[TEXT_PROP_TITLE].font,
1875                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1876                   GFX_H_CENTER, GFX_V_CENTER,
1877                   im->title);
1878
1879     /* graph labels */
1880     if( !(im->extra_flags & NOLEGEND) ) {
1881       for(i=0;i<im->gdes_c;i++){
1882         if(im->gdes[i].legend[0] =='\0')
1883             continue;
1884          
1885         /* im->gdes[i].leg_y is the bottom of the legend */
1886                 X0 = im->gdes[i].leg_x;
1887                 Y0 = im->gdes[i].leg_y;
1888                 /* Box needed? */
1889                 if (       im->gdes[i].gf != GF_GPRINT
1890                         && im->gdes[i].gf != GF_COMMENT) {
1891                     int boxH, boxV;
1892
1893                     boxH = gfx_get_text_width(im->canvas, 0,
1894                                 im->text_prop[TEXT_PROP_AXIS].font,
1895                                 im->text_prop[TEXT_PROP_AXIS].size,
1896                                 im->tabwidth,"M") * 1.25;
1897                     boxV = boxH;
1898
1899                     node = gfx_new_area(im->canvas,
1900                                 X0,Y0-boxV,
1901                                 X0,Y0,
1902                                 X0+boxH,Y0,
1903                                 im->gdes[i].col);
1904                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1905                     node = gfx_new_line(im->canvas,
1906                                 X0,Y0-boxV, X0,Y0,
1907                                 1,0x000000FF);
1908                     gfx_add_point(node,X0+boxH,Y0);
1909                     gfx_add_point(node,X0+boxH,Y0-boxV);
1910                     gfx_close_path(node);
1911                     X0 += boxH / 1.25 * 2;
1912                 }
1913                 gfx_new_text ( im->canvas, X0, Y0,
1914                                    im->graph_col[GRC_FONT],
1915                                    im->text_prop[TEXT_PROP_AXIS].font,
1916                                    im->text_prop[TEXT_PROP_AXIS].size,
1917                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1918                                    im->gdes[i].legend );
1919               }
1920            }
1921         }
1922
1923
1924 /*****************************************************
1925  * lazy check make sure we rely need to create this graph
1926  *****************************************************/
1927
1928 int lazy_check(image_desc_t *im){
1929     FILE *fd = NULL;
1930         int size = 1;
1931     struct stat  imgstat;
1932     
1933     if (im->lazy == 0) return 0; /* no lazy option */
1934     if (stat(im->graphfile,&imgstat) != 0) 
1935       return 0; /* can't stat */
1936     /* one pixel in the existing graph is more then what we would
1937        change here ... */
1938     if (time(NULL) - imgstat.st_mtime > 
1939         (im->end - im->start) / im->xsize) 
1940       return 0;
1941     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1942       return 0; /* the file does not exist */
1943     switch (im->canvas->imgformat) {
1944     case IF_PNG:
1945            size = PngSize(fd,&(im->ximg),&(im->yimg));
1946            break;
1947     default:
1948            size = 1;
1949     }
1950     fclose(fd);
1951     return size;
1952 }
1953
1954 void
1955 pie_part(image_desc_t *im, gfx_color_t color,
1956             double PieCenterX, double PieCenterY, double Radius,
1957             double startangle, double endangle)
1958 {
1959     gfx_node_t *node;
1960     double angle;
1961     double step=M_PI/50; /* Number of iterations for the circle;
1962                          ** 10 is definitely too low, more than
1963                          ** 50 seems to be overkill
1964                          */
1965
1966     /* Strange but true: we have to work clockwise or else
1967     ** anti aliasing nor transparency don't work.
1968     **
1969     ** This test is here to make sure we do it right, also
1970     ** this makes the for...next loop more easy to implement.
1971     ** The return will occur if the user enters a negative number
1972     ** (which shouldn't be done according to the specs) or if the
1973     ** programmers do something wrong (which, as we all know, never
1974     ** happens anyway :)
1975     */
1976     if (endangle<startangle) return;
1977
1978     /* Hidden feature: Radius decreases each full circle */
1979     angle=startangle;
1980     while (angle>=2*M_PI) {
1981         angle  -= 2*M_PI;
1982         Radius *= 0.8;
1983     }
1984
1985     node=gfx_new_area(im->canvas,
1986                 PieCenterX+sin(startangle)*Radius,
1987                 PieCenterY-cos(startangle)*Radius,
1988                 PieCenterX,
1989                 PieCenterY,
1990                 PieCenterX+sin(endangle)*Radius,
1991                 PieCenterY-cos(endangle)*Radius,
1992                 color);
1993     for (angle=endangle;angle-startangle>=step;angle-=step) {
1994         gfx_add_point(node,
1995                 PieCenterX+sin(angle)*Radius,
1996                 PieCenterY-cos(angle)*Radius );
1997     }
1998 }
1999
2000 int
2001 graph_size_location(image_desc_t *im, int elements, int piechart )
2002 {
2003     /* The actual size of the image to draw is determined from
2004     ** several sources.  The size given on the command line is
2005     ** the graph area but we need more as we have to draw labels
2006     ** and other things outside the graph area
2007     */
2008
2009     /* +-+-------------------------------------------+
2010     ** |l|.................title.....................|
2011     ** |e+--+-------------------------------+--------+
2012     ** |b| b|                               |        |
2013     ** |a| a|                               |  pie   |
2014     ** |l| l|          main graph area      | chart  |
2015     ** |.| .|                               |  area  |
2016     ** |t| y|                               |        |
2017     ** |r+--+-------------------------------+--------+
2018     ** |e|  | x-axis labels                 |        |
2019     ** |v+--+-------------------------------+--------+
2020     ** | |..............legends......................|
2021     ** +-+-------------------------------------------+
2022     */
2023     int Xvertical=0,    Yvertical=0,
2024         Xtitle   =0,    Ytitle   =0,
2025         Xylabel  =0,    Yylabel  =0,
2026         Xmain    =0,    Ymain    =0,
2027         Xpie     =0,    Ypie     =0,
2028         Xxlabel  =0,    Yxlabel  =0,
2029 #if 0
2030         Xlegend  =0,    Ylegend  =0,
2031 #endif
2032         Xspacing =10,   Yspacing =10;
2033
2034     if (im->ylegend[0] != '\0') {
2035         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2036         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2037     }
2038
2039     if (im->title[0] != '\0') {
2040         /* The title is placed "inbetween" two text lines so it
2041         ** automatically has some vertical spacing.  The horizontal
2042         ** spacing is added here, on each side.
2043         */
2044         Xtitle = gfx_get_text_width(im->canvas, 0,
2045                 im->text_prop[TEXT_PROP_TITLE].font,
2046                 im->text_prop[TEXT_PROP_TITLE].size,
2047                 im->tabwidth,
2048                 im->title) + 2*Xspacing;
2049         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2050     }
2051
2052     if (elements) {
2053         Xmain=im->xsize;
2054         Ymain=im->ysize;
2055         if (im->draw_x_grid) {
2056             Xxlabel=Xmain;
2057             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2058         }
2059         if (im->draw_y_grid) {
2060             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2061             Yylabel=Ymain;
2062         }
2063     }
2064
2065     if (piechart) {
2066         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2067         Xpie=im->piesize;
2068         Ypie=im->piesize;
2069     }
2070
2071     /* Now calculate the total size.  Insert some spacing where
2072        desired.  im->xorigin and im->yorigin need to correspond
2073        with the lower left corner of the main graph area or, if
2074        this one is not set, the imaginary box surrounding the
2075        pie chart area. */
2076
2077     /* The legend width cannot yet be determined, as a result we
2078     ** have problems adjusting the image to it.  For now, we just
2079     ** forget about it at all; the legend will have to fit in the
2080     ** size already allocated.
2081     */
2082     im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2083     if (Xmain) im->ximg += Xspacing;
2084     if (Xpie) im->ximg += Xspacing;
2085     im->xorigin = Xspacing + Xylabel;
2086     if (Xtitle > im->ximg) im->ximg = Xtitle;
2087     if (Xvertical) {
2088         im->ximg += Xvertical;
2089         im->xorigin += Xvertical;
2090     }
2091     xtr(im,0);
2092
2093     /* The vertical size is interesting... we need to compare
2094     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2095     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2096     ** start even thinking about Ylegend.
2097     **
2098     ** Do it in three portions: First calculate the inner part,
2099     ** then do the legend, then adjust the total height of the img.
2100     */
2101
2102     /* reserve space for main and/or pie */
2103     im->yimg = Ymain + Yxlabel;
2104     if (im->yimg < Ypie) im->yimg = Ypie;
2105     im->yorigin = im->yimg - Yxlabel;
2106     /* reserve space for the title *or* some padding above the graph */
2107     if (Ytitle) {
2108         im->yimg += Ytitle;
2109         im->yorigin += Ytitle;
2110     } else {
2111         im->yimg += Yspacing;
2112         im->yorigin += Yspacing;
2113     }
2114     /* reserve space for padding below the graph */
2115     im->yimg += Yspacing;
2116     ytr(im,DNAN);
2117
2118     /* Determine where to place the legends onto the image.
2119     ** Adjust im->yimg to match the space requirements.
2120     */
2121     if(leg_place(im)==-1)
2122         return -1;
2123
2124     /* last of three steps: check total height of image */
2125     if (im->yimg < Yvertical) im->yimg = Yvertical;
2126
2127 #if 0
2128     if (Xlegend > im->ximg) {
2129         im->ximg = Xlegend;
2130         /* reposition Pie */
2131 #endif
2132
2133     /* The pie is placed in the upper right hand corner,
2134     ** just below the title (if any) and with sufficient
2135     ** padding.
2136     */
2137     if (elements) {
2138         im->pie_x = im->ximg - Xspacing - Xpie/2;
2139         im->pie_y = im->yorigin-Ymain+Ypie/2;
2140     } else {
2141         im->pie_x = im->ximg/2;
2142         im->pie_y = im->yorigin-Ypie/2;
2143     }
2144
2145     return 0;
2146 }
2147
2148 /* draw that picture thing ... */
2149 int
2150 graph_paint(image_desc_t *im, char ***calcpr)
2151 {
2152   int i,ii;
2153   int lazy =     lazy_check(im);
2154   int piechart = 0;
2155   double PieStart=0.0;
2156   FILE  *fo;
2157   gfx_node_t *node;
2158   
2159   double areazero = 0.0;
2160   enum gf_en stack_gf = GF_PRINT;
2161   graph_desc_t *lastgdes = NULL;    
2162   
2163   /* if we are lazy and there is nothing to PRINT ... quit now */
2164   if (lazy && im->prt_c==0) return 0;
2165   
2166   /* pull the data from the rrd files ... */
2167   
2168   if(data_fetch(im)==-1)
2169     return -1;
2170   
2171   /* evaluate VDEF and CDEF operations ... */
2172   if(data_calc(im)==-1)
2173     return -1;
2174   
2175   /* check if we need to draw a piechart */
2176   for(i=0;i<im->gdes_c;i++){
2177     if (im->gdes[i].gf == GF_PART) {
2178       piechart=1;
2179       break;
2180     }
2181   }
2182
2183   /* calculate and PRINT and GPRINT definitions. We have to do it at
2184    * this point because it will affect the length of the legends
2185    * if there are no graph elements we stop here ... 
2186    * if we are lazy, try to quit ... 
2187    */
2188   i=print_calc(im,calcpr);
2189   if(i<0) return -1;
2190   if(((i==0)&&(piechart==0)) || lazy) return 0;
2191
2192   /* If there's only the pie chart to draw, signal this */
2193   if (i==0) piechart=2;
2194   
2195   /* get actual drawing data and find min and max values*/
2196   if(data_proc(im)==-1)
2197     return -1;
2198   
2199   if(!im->logarithmic){si_unit(im);}        /* identify si magnitude Kilo, Mega Giga ? */
2200   
2201   if(!im->rigid && ! im->logarithmic)
2202     expand_range(im);   /* make sure the upper and lower limit are
2203                            sensible values */
2204
2205   if (!calc_horizontal_grid(im))
2206     return -1;
2207   if (im->gridfit)
2208     apply_gridfit(im);
2209
2210 /**************************************************************
2211  *** Calculating sizes and locations became a bit confusing ***
2212  *** so I moved this into a separate function.              ***
2213  **************************************************************/
2214   if(graph_size_location(im,i,piechart)==-1)
2215     return -1;
2216
2217   /* the actual graph is created by going through the individual
2218      graph elements and then drawing them */
2219   
2220   node=gfx_new_area ( im->canvas,
2221                       0, 0,
2222                       im->ximg, 0,
2223                       im->ximg, im->yimg,
2224                       im->graph_col[GRC_BACK]);
2225
2226   gfx_add_point(node,0, im->yimg);
2227
2228   if (piechart != 2) {
2229     node=gfx_new_area ( im->canvas,
2230                       im->xorigin,             im->yorigin, 
2231                       im->xorigin + im->xsize, im->yorigin,
2232                       im->xorigin + im->xsize, im->yorigin-im->ysize,
2233                       im->graph_col[GRC_CANVAS]);
2234   
2235     gfx_add_point(node,im->xorigin, im->yorigin - im->ysize);
2236
2237     if (im->minval > 0.0)
2238       areazero = im->minval;
2239     if (im->maxval < 0.0)
2240       areazero = im->maxval;
2241   
2242     axis_paint(im);
2243   }
2244
2245   if (piechart) {
2246     pie_part(im,im->graph_col[GRC_CANVAS],im->pie_x,im->pie_y,im->piesize*0.5,0,2*M_PI);
2247   }
2248
2249   for(i=0;i<im->gdes_c;i++){
2250     switch(im->gdes[i].gf){
2251     case GF_CDEF:
2252     case GF_VDEF:
2253     case GF_DEF:
2254     case GF_PRINT:
2255     case GF_GPRINT:
2256     case GF_COMMENT:
2257     case GF_HRULE:
2258     case GF_VRULE:
2259       break;
2260     case GF_TICK:
2261       for (ii = 0; ii < im->xsize; ii++)
2262         {
2263           if (!isnan(im->gdes[i].p_data[ii]) && 
2264               im->gdes[i].p_data[ii] > 0.0)
2265             { 
2266               /* generate a tick */
2267               gfx_new_line(im->canvas, im -> xorigin + ii, 
2268                            im -> yorigin - (im -> gdes[i].yrule * im -> ysize),
2269                            im -> xorigin + ii, 
2270                            im -> yorigin,
2271                            1.0,
2272                            im -> gdes[i].col );
2273             }
2274         }
2275       break;
2276     case GF_LINE:
2277     case GF_AREA:
2278       stack_gf = im->gdes[i].gf;
2279     case GF_STACK:          
2280       /* fix data points at oo and -oo */
2281       for(ii=0;ii<im->xsize;ii++){
2282         if (isinf(im->gdes[i].p_data[ii])){
2283           if (im->gdes[i].p_data[ii] > 0) {
2284             im->gdes[i].p_data[ii] = im->maxval ;
2285           } else {
2286             im->gdes[i].p_data[ii] = im->minval ;
2287           }                 
2288           
2289         }
2290       } /* for */
2291       
2292       if (im->gdes[i].col != 0x0){               
2293         /* GF_LINE and friend */
2294         if(stack_gf == GF_LINE ){
2295           node = NULL;
2296           for(ii=1;ii<im->xsize;ii++){
2297             if ( ! isnan(im->gdes[i].p_data[ii-1])
2298                  && ! isnan(im->gdes[i].p_data[ii])){
2299               if (node == NULL){
2300                 node = gfx_new_line(im->canvas,
2301                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2302                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2303                                     im->gdes[i].linewidth,
2304                                     im->gdes[i].col);
2305               } else {
2306                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2307               }
2308             } else {
2309               node = NULL;
2310             }
2311           }
2312         } else {
2313           int area_start=-1;
2314           node = NULL;
2315           for(ii=1;ii<im->xsize;ii++){
2316             /* open an area */
2317             if ( ! isnan(im->gdes[i].p_data[ii-1])
2318                  && ! isnan(im->gdes[i].p_data[ii])){
2319               if (node == NULL){
2320                 float ybase = 0.0;
2321                 if (im->gdes[i].gf == GF_STACK) {
2322                   ybase = ytr(im,lastgdes->p_data[ii-1]);
2323                 } else {
2324                   ybase =  ytr(im,areazero);
2325                 }
2326                 area_start = ii-1;
2327                 node = gfx_new_area(im->canvas,
2328                                     ii-1+im->xorigin,ybase,
2329                                     ii-1+im->xorigin,ytr(im,im->gdes[i].p_data[ii-1]),
2330                                     ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]),
2331                                     im->gdes[i].col
2332                                     );
2333               } else {
2334                 gfx_add_point(node,ii+im->xorigin,ytr(im,im->gdes[i].p_data[ii]));
2335               }
2336             }
2337
2338             if ( node != NULL && (ii+1==im->xsize || isnan(im->gdes[i].p_data[ii]) )){
2339               /* GF_AREA STACK type*/
2340               if (im->gdes[i].gf == GF_STACK ) {
2341                 int iii;
2342                 for (iii=ii-1;iii>area_start;iii--){
2343                   gfx_add_point(node,iii+im->xorigin,ytr(im,lastgdes->p_data[iii]));
2344                 }
2345               } else {
2346                 gfx_add_point(node,ii+im->xorigin,ytr(im,areazero));
2347               };
2348               node=NULL;
2349             };
2350           }             
2351         } /* else GF_LINE */
2352       } /* if color != 0x0 */
2353       /* make sure we do not run into trouble when stacking on NaN */
2354       for(ii=0;ii<im->xsize;ii++){
2355         if (isnan(im->gdes[i].p_data[ii])) {
2356           double ybase = 0.0;
2357           if (lastgdes) {
2358             ybase = ytr(im,lastgdes->p_data[ii-1]);
2359           };
2360           if (isnan(ybase) || !lastgdes ){
2361             ybase =  ytr(im,areazero);
2362           }
2363           im->gdes[i].p_data[ii] = ybase;
2364         }
2365       } 
2366       lastgdes = &(im->gdes[i]);                         
2367       break;
2368     case GF_PART:
2369       if(isnan(im->gdes[i].yrule)) /* fetch variable */
2370         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2371      
2372       if (finite(im->gdes[i].yrule)) {  /* even the fetched var can be NaN */
2373         pie_part(im,im->gdes[i].col,
2374                 im->pie_x,im->pie_y,im->piesize*0.4,
2375                 M_PI*2.0*PieStart/100.0,
2376                 M_PI*2.0*(PieStart+im->gdes[i].yrule)/100.0);
2377         PieStart += im->gdes[i].yrule;
2378       }
2379       break;
2380     } /* switch */
2381   }
2382   if (piechart==2) {
2383     im->draw_x_grid=0;
2384     im->draw_y_grid=0;
2385   }
2386   /* grid_paint also does the text */
2387   grid_paint(im);
2388   
2389   /* the RULES are the last thing to paint ... */
2390   for(i=0;i<im->gdes_c;i++){    
2391     
2392     switch(im->gdes[i].gf){
2393     case GF_HRULE:
2394       if(isnan(im->gdes[i].yrule)) { /* fetch variable */
2395         im->gdes[i].yrule = im->gdes[im->gdes[i].vidx].vf.val;
2396       };
2397       if(im->gdes[i].yrule >= im->minval
2398          && im->gdes[i].yrule <= im->maxval)
2399         gfx_new_line(im->canvas,
2400                      im->xorigin,ytr(im,im->gdes[i].yrule),
2401                      im->xorigin+im->xsize,ytr(im,im->gdes[i].yrule),
2402                      1.0,im->gdes[i].col); 
2403       break;
2404     case GF_VRULE:
2405       if(im->gdes[i].xrule == 0) { /* fetch variable */
2406         im->gdes[i].xrule = im->gdes[im->gdes[i].vidx].vf.when;
2407       };
2408       if(im->gdes[i].xrule >= im->start
2409          && im->gdes[i].xrule <= im->end)
2410         gfx_new_line(im->canvas,
2411                      xtr(im,im->gdes[i].xrule),im->yorigin,
2412                      xtr(im,im->gdes[i].xrule),im->yorigin-im->ysize,
2413                      1.0,im->gdes[i].col); 
2414       break;
2415     default:
2416       break;
2417     }
2418   }
2419
2420   
2421   if (strcmp(im->graphfile,"-")==0) {
2422 #ifdef WIN32
2423     /* Change translation mode for stdout to BINARY */
2424     _setmode( _fileno( stdout ), O_BINARY );
2425 #endif
2426     fo = stdout;
2427   } else {
2428     if ((fo = fopen(im->graphfile,"wb")) == NULL) {
2429       rrd_set_error("Opening '%s' for write: %s",im->graphfile,
2430                     strerror(errno));
2431       return (-1);
2432     }
2433   }
2434   gfx_render (im->canvas,im->ximg,im->yimg,0x0,fo);
2435   if (strcmp(im->graphfile,"-") != 0)
2436     fclose(fo);
2437   return 0;
2438 }
2439
2440
2441 /*****************************************************
2442  * graph stuff 
2443  *****************************************************/
2444
2445 int
2446 gdes_alloc(image_desc_t *im){
2447
2448     long def_step = (im->end-im->start)/im->xsize;
2449     
2450     if (im->step > def_step) /* step can be increassed ... no decreassed */
2451       def_step = im->step;
2452
2453     im->gdes_c++;
2454     
2455     if ((im->gdes = (graph_desc_t *) rrd_realloc(im->gdes, (im->gdes_c)
2456                                            * sizeof(graph_desc_t)))==NULL){
2457         rrd_set_error("realloc graph_descs");
2458         return -1;
2459     }
2460
2461
2462     im->gdes[im->gdes_c-1].step=def_step; 
2463     im->gdes[im->gdes_c-1].start=im->start; 
2464     im->gdes[im->gdes_c-1].end=im->end; 
2465     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2466     im->gdes[im->gdes_c-1].data=NULL;
2467     im->gdes[im->gdes_c-1].ds_namv=NULL;
2468     im->gdes[im->gdes_c-1].data_first=0;
2469     im->gdes[im->gdes_c-1].p_data=NULL;
2470     im->gdes[im->gdes_c-1].rpnp=NULL;
2471     im->gdes[im->gdes_c-1].col = 0x0;
2472     im->gdes[im->gdes_c-1].legend[0]='\0';
2473     im->gdes[im->gdes_c-1].rrd[0]='\0';
2474     im->gdes[im->gdes_c-1].ds=-1;    
2475     im->gdes[im->gdes_c-1].p_data=NULL;    
2476     return 0;
2477 }
2478
2479 /* copies input untill the first unescaped colon is found
2480    or until input ends. backslashes have to be escaped as well */
2481 int
2482 scan_for_col(char *input, int len, char *output)
2483 {
2484     int inp,outp=0;
2485     for (inp=0; 
2486          inp < len &&
2487            input[inp] != ':' &&
2488            input[inp] != '\0';
2489          inp++){
2490       if (input[inp] == '\\' &&
2491           input[inp+1] != '\0' && 
2492           (input[inp+1] == '\\' ||
2493            input[inp+1] == ':')){
2494         output[outp++] = input[++inp];
2495       }
2496       else {
2497         output[outp++] = input[inp];
2498       }
2499     }
2500     output[outp] = '\0';
2501     return inp;
2502 }
2503
2504 /* Some surgery done on this function, it became ridiculously big.
2505 ** Things moved:
2506 ** - initializing     now in rrd_graph_init()
2507 ** - options parsing  now in rrd_graph_options()
2508 ** - script parsing   now in rrd_graph_script()
2509 */
2510 int 
2511 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2512 {
2513     image_desc_t   im;
2514
2515 #ifdef HAVE_TZSET
2516     tzset();
2517 #endif
2518 #ifdef HAVE_SETLOCALE
2519     setlocale(LC_TIME,"");
2520 #endif
2521             
2522             
2523     rrd_graph_init(&im);
2524
2525     rrd_graph_options(argc,argv,&im);
2526     if (rrd_test_error()) return -1;
2527     
2528     if (strlen(argv[optind])>=MAXPATH) {
2529         rrd_set_error("filename (including path) too long");
2530         return -1;
2531     }
2532     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2533     im.graphfile[MAXPATH-1]='\0';
2534
2535     rrd_graph_script(argc,argv,&im);
2536     if (rrd_test_error()) return -1;
2537
2538     /* Everything is now read and the actual work can start */
2539
2540     (*prdata)=NULL;
2541     if (graph_paint(&im,prdata)==-1){
2542         im_free(&im);
2543         return -1;
2544     }
2545
2546     /* The image is generated and needs to be output.
2547     ** Also, if needed, print a line with information about the image.
2548     */
2549
2550     *xsize=im.ximg;
2551     *ysize=im.yimg;
2552     if (im.imginfo) {
2553         char *filename;
2554         if (!(*prdata)) {
2555             /* maybe prdata is not allocated yet ... lets do it now */
2556             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2557                 rrd_set_error("malloc imginfo");
2558                 return -1; 
2559             };
2560         }
2561         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2562          ==NULL){
2563             rrd_set_error("malloc imginfo");
2564             return -1;
2565         }
2566         filename=im.graphfile+strlen(im.graphfile);
2567         while(filename > im.graphfile) {
2568             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2569             filename--;
2570         }
2571
2572         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2573     }
2574     im_free(&im);
2575     return 0;
2576 }
2577
2578 void
2579 rrd_graph_init(image_desc_t *im)
2580 {
2581     int i;
2582
2583     im->xlab_user.minsec = -1;
2584     im->ximg=0;
2585     im->yimg=0;
2586     im->xsize = 400;
2587     im->ysize = 100;
2588     im->step = 0;
2589     im->ylegend[0] = '\0';
2590     im->title[0] = '\0';
2591     im->minval = DNAN;
2592     im->maxval = DNAN;    
2593     im->unitsexponent= 9999;
2594     im->extra_flags= 0;
2595     im->rigid = 0;
2596     im->gridfit = 1;
2597     im->imginfo = NULL;
2598     im->lazy = 0;
2599     im->logarithmic = 0;
2600     im->ygridstep = DNAN;
2601     im->draw_x_grid = 1;
2602     im->draw_y_grid = 1;
2603     im->base = 1000;
2604     im->prt_c = 0;
2605     im->gdes_c = 0;
2606     im->gdes = NULL;
2607     im->canvas = gfx_new_canvas();
2608
2609     for(i=0;i<DIM(graph_col);i++)
2610         im->graph_col[i]=graph_col[i];
2611
2612     for(i=0;i<DIM(text_prop);i++){        
2613       im->text_prop[i].size = text_prop[i].size;
2614       im->text_prop[i].font = text_prop[i].font;
2615     }
2616 }
2617
2618 void
2619 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2620 {
2621     int                 stroff;    
2622     char                *parsetime_error = NULL;
2623     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2624     time_t              start_tmp=0,end_tmp=0;
2625     long                long_tmp;
2626     struct time_value   start_tv, end_tv;
2627     gfx_color_t         color;
2628
2629     parsetime("end-24h", &start_tv);
2630     parsetime("now", &end_tv);
2631
2632     while (1){
2633         static struct option long_options[] =
2634         {
2635             {"start",      required_argument, 0,  's'},
2636             {"end",        required_argument, 0,  'e'},
2637             {"x-grid",     required_argument, 0,  'x'},
2638             {"y-grid",     required_argument, 0,  'y'},
2639             {"vertical-label",required_argument,0,'v'},
2640             {"width",      required_argument, 0,  'w'},
2641             {"height",     required_argument, 0,  'h'},
2642             {"interlaced", no_argument,       0,  'i'},
2643             {"upper-limit",required_argument, 0,  'u'},
2644             {"lower-limit",required_argument, 0,  'l'},
2645             {"rigid",      no_argument,       0,  'r'},
2646             {"base",       required_argument, 0,  'b'},
2647             {"logarithmic",no_argument,       0,  'o'},
2648             {"color",      required_argument, 0,  'c'},
2649             {"font",       required_argument, 0,  'n'},
2650             {"title",      required_argument, 0,  't'},
2651             {"imginfo",    required_argument, 0,  'f'},
2652             {"imgformat",  required_argument, 0,  'a'},
2653             {"lazy",       no_argument,       0,  'z'},
2654             {"zoom",       required_argument, 0,  'm'},
2655             {"no-legend",  no_argument,       0,  'g'},
2656             {"alt-y-grid", no_argument,       0,   257 },
2657             {"alt-autoscale", no_argument,    0,   258 },
2658             {"alt-autoscale-max", no_argument,    0,   259 },
2659             {"units-exponent",required_argument, 0,  260},
2660             {"step",       required_argument, 0,   261},
2661             {"no-gridfit", no_argument,       0,   262},
2662             {0,0,0,0}};
2663         int option_index = 0;
2664         int opt;
2665
2666
2667         opt = getopt_long(argc, argv, 
2668                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2669                           long_options, &option_index);
2670
2671         if (opt == EOF)
2672             break;
2673         
2674         switch(opt) {
2675         case 257:
2676             im->extra_flags |= ALTYGRID;
2677             break;
2678         case 258:
2679             im->extra_flags |= ALTAUTOSCALE;
2680             break;
2681         case 259:
2682             im->extra_flags |= ALTAUTOSCALE_MAX;
2683             break;
2684         case 'g':
2685             im->extra_flags |= NOLEGEND;
2686             break;
2687         case 260:
2688             im->unitsexponent = atoi(optarg);
2689             break;
2690         case 261:
2691             im->step =  atoi(optarg);
2692             break;
2693         case 262:
2694             im->gridfit = 0;
2695             break;
2696         case 's':
2697             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2698                 rrd_set_error( "start time: %s", parsetime_error );
2699                 return;
2700             }
2701             break;
2702         case 'e':
2703             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2704                 rrd_set_error( "end time: %s", parsetime_error );
2705                 return;
2706             }
2707             break;
2708         case 'x':
2709             if(strcmp(optarg,"none") == 0){
2710               im->draw_x_grid=0;
2711               break;
2712             };
2713                 
2714             if(sscanf(optarg,
2715                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2716                       scan_gtm,
2717                       &im->xlab_user.gridst,
2718                       scan_mtm,
2719                       &im->xlab_user.mgridst,
2720                       scan_ltm,
2721                       &im->xlab_user.labst,
2722                       &im->xlab_user.precis,
2723                       &stroff) == 7 && stroff != 0){
2724                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2725                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2726                     rrd_set_error("unknown keyword %s",scan_gtm);
2727                     return;
2728                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2729                     rrd_set_error("unknown keyword %s",scan_mtm);
2730                     return;
2731                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2732                     rrd_set_error("unknown keyword %s",scan_ltm);
2733                     return;
2734                 } 
2735                 im->xlab_user.minsec = 1;
2736                 im->xlab_user.stst = im->xlab_form;
2737             } else {
2738                 rrd_set_error("invalid x-grid format");
2739                 return;
2740             }
2741             break;
2742         case 'y':
2743
2744             if(strcmp(optarg,"none") == 0){
2745               im->draw_y_grid=0;
2746               break;
2747             };
2748
2749             if(sscanf(optarg,
2750                       "%lf:%d",
2751                       &im->ygridstep,
2752                       &im->ylabfact) == 2) {
2753                 if(im->ygridstep<=0){
2754                     rrd_set_error("grid step must be > 0");
2755                     return;
2756                 } else if (im->ylabfact < 1){
2757                     rrd_set_error("label factor must be > 0");
2758                     return;
2759                 } 
2760             } else {
2761                 rrd_set_error("invalid y-grid format");
2762                 return;
2763             }
2764             break;
2765         case 'v':
2766             strncpy(im->ylegend,optarg,150);
2767             im->ylegend[150]='\0';
2768             break;
2769         case 'u':
2770             im->maxval = atof(optarg);
2771             break;
2772         case 'l':
2773             im->minval = atof(optarg);
2774             break;
2775         case 'b':
2776             im->base = atol(optarg);
2777             if(im->base != 1024 && im->base != 1000 ){
2778                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2779                 return;
2780             }
2781             break;
2782         case 'w':
2783             long_tmp = atol(optarg);
2784             if (long_tmp < 10) {
2785                 rrd_set_error("width below 10 pixels");
2786                 return;
2787             }
2788             im->xsize = long_tmp;
2789             break;
2790         case 'h':
2791             long_tmp = atol(optarg);
2792             if (long_tmp < 10) {
2793                 rrd_set_error("height below 10 pixels");
2794                 return;
2795             }
2796             im->ysize = long_tmp;
2797             break;
2798         case 'i':
2799             im->canvas->interlaced = 1;
2800             break;
2801         case 'r':
2802             im->rigid = 1;
2803             break;
2804         case 'f':
2805             im->imginfo = optarg;
2806             break;
2807         case 'a':
2808             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2809                 rrd_set_error("unsupported graphics format '%s'",optarg);
2810                 return;
2811             }
2812             break;
2813         case 'z':
2814             im->lazy = 1;
2815             break;
2816         case 'o':
2817             im->logarithmic = 1;
2818             if (isnan(im->minval))
2819                 im->minval=1;
2820             break;
2821         case 'c':
2822             if(sscanf(optarg,
2823                       "%10[A-Z]#%8lx",
2824                       col_nam,&color) == 2){
2825                 int ci;
2826                 if((ci=grc_conv(col_nam)) != -1){
2827                     im->graph_col[ci]=color;
2828                 }  else {
2829                   rrd_set_error("invalid color name '%s'",col_nam);
2830                 }
2831             } else {
2832                 rrd_set_error("invalid color def format");
2833                 return;
2834             }
2835             break;        
2836         case 'n':{
2837                         /* originally this used char *prop = "" and
2838                         ** char *font = "dummy" however this results
2839                         ** in a SEG fault, at least on RH7.1
2840                         **
2841                         ** The current implementation isn't proper
2842                         ** either, font is never freed and prop uses
2843                         ** a fixed width string
2844                         */
2845             char prop[100];
2846             double size = 1;
2847             char *font;
2848
2849             font=malloc(255);
2850             if(sscanf(optarg,
2851                                 "%10[A-Z]:%lf:%s",
2852                                 prop,&size,font) == 3){
2853                 int sindex;
2854                 if((sindex=text_prop_conv(prop)) != -1){
2855                     im->text_prop[sindex].size=size;              
2856                     im->text_prop[sindex].font=font;
2857                     if (sindex==0) { /* the default */
2858                         im->text_prop[TEXT_PROP_TITLE].size=size;
2859                         im->text_prop[TEXT_PROP_TITLE].font=font;
2860                         im->text_prop[TEXT_PROP_AXIS].size=size;
2861                         im->text_prop[TEXT_PROP_AXIS].font=font;
2862                         im->text_prop[TEXT_PROP_UNIT].size=size;
2863                         im->text_prop[TEXT_PROP_UNIT].font=font;
2864                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2865                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2866                     }
2867                 } else {
2868                     rrd_set_error("invalid fonttag '%s'",prop);
2869                     return;
2870                 }
2871             } else {
2872                 rrd_set_error("invalid text property format");
2873                 return;
2874             }
2875             break;          
2876         }
2877         case 'm':
2878             im->canvas->zoom = atof(optarg);
2879             if (im->canvas->zoom <= 0.0) {
2880                 rrd_set_error("zoom factor must be > 0");
2881                 return;
2882             }
2883           break;
2884         case 't':
2885             strncpy(im->title,optarg,150);
2886             im->title[150]='\0';
2887             break;
2888
2889         case '?':
2890             if (optopt != 0)
2891                 rrd_set_error("unknown option '%c'", optopt);
2892             else
2893                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2894             return;
2895         }
2896     }
2897     
2898     if (optind >= argc) {
2899        rrd_set_error("missing filename");
2900        return;
2901     }
2902
2903     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2904         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2905         return;
2906     }
2907
2908     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2909         /* error string is set in parsetime.c */
2910         return;
2911     }  
2912     
2913     if (start_tmp < 3600*24*365*10){
2914         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2915         return;
2916     }
2917     
2918     if (end_tmp < start_tmp) {
2919         rrd_set_error("start (%ld) should be less than end (%ld)", 
2920                start_tmp, end_tmp);
2921         return;
2922     }
2923     
2924     im->start = start_tmp;
2925     im->end = end_tmp;
2926 }
2927
2928 void
2929 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
2930 {
2931     int         i;
2932     char        symname[100];
2933     int         linepass = 0; /* stack must follow LINE*, AREA or STACK */    
2934
2935     for (i=optind+1;i<argc;i++) {
2936         int             argstart=0;
2937         int             strstart=0;
2938         graph_desc_t    *gdp;
2939         char            *line;
2940         char            funcname[10],vname[MAX_VNAME_LEN+1],sep[1];
2941         double          d;
2942         double          linewidth;
2943         int             j,k,l,m;
2944
2945         /* Each command is one element from *argv[], we call this "line".
2946         **
2947         ** Each command defines the most current gdes inside struct im.
2948         ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
2949         */
2950         gdes_alloc(im);
2951         gdp=&im->gdes[im->gdes_c-1];
2952         line=argv[i];
2953
2954         /* function:newvname=string[:ds-name:CF]        for xDEF
2955         ** function:vname[#color[:string]]              for LINEx,AREA,STACK
2956         ** function:vname#color[:num[:string]]          for TICK
2957         ** function:vname-or-num#color[:string]         for xRULE,PART
2958         ** function:vname:CF:string                     for xPRINT
2959         ** function:string                              for COMMENT
2960         */
2961         argstart=0;
2962
2963         sscanf(line, "%10[A-Z0-9]:%n", funcname,&argstart);
2964         if (argstart==0) {
2965             rrd_set_error("Cannot parse function in line: %s",line);
2966             im_free(im);
2967             return;
2968         }
2969         if(sscanf(funcname,"LINE%lf",&linewidth)){
2970                 im->gdes[im->gdes_c-1].gf = GF_LINE;
2971                 im->gdes[im->gdes_c-1].linewidth = linewidth;
2972         } else {
2973           if ((gdp->gf=gf_conv(funcname))==-1) {
2974               rrd_set_error("'%s' is not a valid function name",funcname);
2975               im_free(im);
2976               return;
2977           }
2978         }
2979
2980         /* If the error string is set, we exit at the end of the switch */
2981         switch (gdp->gf) {
2982             case GF_COMMENT:
2983                 if (rrd_graph_legend(gdp,&line[argstart])==0)
2984                     rrd_set_error("Cannot parse comment in line: %s",line);
2985                 break;
2986             case GF_PART:
2987             case GF_VRULE:
2988             case GF_HRULE:
2989                 j=k=l=m=0;
2990                 sscanf(&line[argstart], "%lf%n#%n", &d, &j, &k);
2991                 sscanf(&line[argstart], DEF_NAM_FMT "%n#%n", vname, &l, &m);
2992                 if (k+m==0) {
2993                     rrd_set_error("Cannot parse name or num in line: %s",line);
2994                     break;
2995                 }
2996                 if (j!=0) {
2997                     gdp->xrule=d;
2998                     gdp->yrule=d;
2999                     argstart+=j;
3000                 } else if (!rrd_graph_check_vname(im,vname,line)) {
3001                     gdp->xrule=0;
3002                     gdp->yrule=DNAN;
3003                     argstart+=l;
3004                 } else break; /* exit due to wrong vname */
3005                 if ((j=rrd_graph_color(im,&line[argstart],line,0))==0) break;
3006                 argstart+=j;
3007                 if (strlen(&line[argstart])!=0) {
3008                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
3009                         rrd_set_error("Cannot parse comment in line: %s",line);
3010                 }
3011                 break;
3012             case GF_STACK:
3013                 if (linepass==0) {
3014                     rrd_set_error("STACK must follow another graphing element");
3015                     break;
3016                 }
3017             case GF_LINE:
3018             case GF_AREA:
3019             case GF_TICK:
3020                 j=k=0;
3021                 linepass=1;
3022                 sscanf(&line[argstart],DEF_NAM_FMT"%n%1[#:]%n",vname,&j,sep,&k);
3023                 if (j+1!=k)
3024                     rrd_set_error("Cannot parse vname in line: %s",line);
3025                 else if (rrd_graph_check_vname(im,vname,line))
3026                     rrd_set_error("Undefined vname '%s' in line: %s",line);
3027                 else
3028                     k=rrd_graph_color(im,&line[argstart],line,1);
3029                 if (rrd_test_error()) break;
3030                 argstart=argstart+j+k;
3031                 if ((strlen(&line[argstart])!=0)&&(gdp->gf==GF_TICK)) {
3032                     j=0;
3033                     sscanf(&line[argstart], ":%lf%n", &gdp->yrule,&j);
3034                     argstart+=j;
3035                 }
3036                 if (strlen(&line[argstart])!=0)
3037                     if (rrd_graph_legend(gdp,&line[++argstart])==0)
3038                         rrd_set_error("Cannot parse legend in line: %s",line);
3039                 break;
3040             case GF_PRINT:
3041                 im->prt_c++;
3042             case GF_GPRINT:
3043                 j=0;
3044                 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3045                 if (j==0) {
3046                     rrd_set_error("Cannot parse vname in line: '%s'",line);
3047                     break;
3048                 }
3049                 argstart+=j;
3050                 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3051                 j=0;
3052                 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3053
3054                 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3055 #define VIDX im->gdes[gdp->vidx]
3056                 switch (k) {
3057                     case -1: /* looks CF but is not really CF */
3058                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3059                         break;
3060                     case  0: /* CF present and correct */
3061                         if (VIDX.gf == GF_VDEF)
3062                             rrd_set_error("Don't use CF when printing VDEF");
3063                         argstart+=j;
3064                         break;
3065                     case  1: /* CF not present */
3066                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3067                         else rrd_set_error("Printing DEF or CDEF needs CF");
3068                         break;
3069                     default:
3070                         rrd_set_error("Oops, bug in GPRINT scanning");
3071                 }
3072 #undef VIDX
3073                 if (rrd_test_error()) break;
3074
3075                 if (strlen(&line[argstart])!=0) {
3076                     if (rrd_graph_legend(gdp,&line[argstart])==0)
3077                         rrd_set_error("Cannot parse legend in line: %s",line);
3078                 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3079                 strcpy(gdp->format, gdp->legend);
3080                 break;
3081             case GF_DEF:
3082             case GF_VDEF:
3083             case GF_CDEF:
3084                 j=0;
3085                 sscanf(&line[argstart], DEF_NAM_FMT "=%n",gdp->vname,&j);
3086                 if (j==0) {
3087                     rrd_set_error("Could not parse line: %s",line);
3088                     break;
3089                 }
3090                 if (find_var(im,gdp->vname)!=-1) {
3091                     rrd_set_error("Variable '%s' in line '%s' already in use\n",
3092                                                         gdp->vname,line);
3093                     break;
3094                 }
3095                 argstart+=j;
3096                 switch (gdp->gf) {
3097                     case GF_DEF:
3098                         argstart+=scan_for_col(&line[argstart],MAXPATH,gdp->rrd);
3099                         j=k=0;
3100                         sscanf(&line[argstart],
3101                                 ":" DS_NAM_FMT ":" CF_NAM_FMT "%n%*s%n",
3102                                 gdp->ds_nam, symname, &j, &k);
3103                         if ((j==0)||(k!=0)) {
3104                             rrd_set_error("Cannot parse DS or CF in '%s'",line);
3105                             break;
3106                         }
3107                         rrd_graph_check_CF(im,symname,line);
3108                         break;
3109                     case GF_VDEF:
3110                         j=0;
3111                         sscanf(&line[argstart],DEF_NAM_FMT ",%n",vname,&j);
3112                         if (j==0) {
3113                             rrd_set_error("Cannot parse vname in line '%s'",line);
3114                             break;
3115                         }
3116                         argstart+=j;
3117                         if (rrd_graph_check_vname(im,vname,line)) return;
3118                         if (       im->gdes[gdp->vidx].gf != GF_DEF
3119                                 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3120                             rrd_set_error("variable '%s' not DEF nor "
3121                                 "CDEF in VDEF '%s'", vname,gdp->vname);
3122                             break;
3123                         }
3124                         vdef_parse(gdp,&line[argstart+strstart]);
3125                         break;
3126                     case GF_CDEF:
3127                         if (strstr(&line[argstart],":")!=NULL) {
3128                             rrd_set_error("Error in RPN, line: %s",line);
3129                             break;
3130                         }
3131                         if ((gdp->rpnp = rpn_parse(
3132                                                 (void *)im,
3133                                                 &line[argstart],
3134                                                 &find_var_wrapper)
3135                                 )==NULL)
3136                             rrd_set_error("invalid rpn expression in: %s",line);
3137                         break;
3138                     default: break;
3139                 }
3140                 break;
3141             default: rrd_set_error("Big oops");
3142         }
3143         if (rrd_test_error()) {
3144             im_free(im);
3145             return;
3146         }
3147     }
3148
3149     if (im->gdes_c==0){
3150         rrd_set_error("can't make a graph without contents");
3151         im_free(im); /* ??? is this set ??? */
3152         return; 
3153     }
3154 }
3155 int
3156 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3157 {
3158     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3159         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3160         return -1;
3161     }
3162     return 0;
3163 }
3164 int
3165 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3166 {
3167     char *color;
3168     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3169
3170     color=strstr(var,"#");
3171     if (color==NULL) {
3172         if (optional==0) {
3173             rrd_set_error("Found no color in %s",err);
3174             return 0;
3175         }
3176         return 0;
3177     } else {
3178         int n=0;
3179         char *rest;
3180         gfx_color_t    col;
3181
3182         rest=strstr(color,":");
3183         if (rest!=NULL)
3184             n=rest-color;
3185         else
3186             n=strlen(color);
3187
3188         switch (n) {
3189             case 7:
3190                 sscanf(color,"#%6lx%n",&col,&n);
3191                 col = (col << 8) + 0xff /* shift left by 8 */;
3192                 if (n!=7) rrd_set_error("Color problem in %s",err);
3193                 break;
3194             case 9:
3195                 sscanf(color,"#%8lx%n",&col,&n);
3196                 if (n==9) break;
3197             default:
3198                 rrd_set_error("Color problem in %s",err);
3199         }
3200         if (rrd_test_error()) return 0;
3201         gdp->col = col;
3202         return n;
3203     }
3204 }
3205 int
3206 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3207 {
3208     if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3209         rrd_set_error("Unknown CF '%s' in %s",symname,err);
3210         return -1;
3211     }
3212     return 0;
3213 }
3214 int
3215 rrd_graph_legend(graph_desc_t *gdp, char *line)
3216 {
3217     int i;
3218
3219     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3220
3221     return (strlen(&line[i])==0);
3222 }
3223
3224
3225 int bad_format(char *fmt) {
3226         char *ptr;
3227         int n=0;
3228
3229         ptr = fmt;
3230         while (*ptr != '\0') {
3231                 if (*ptr == '%') {ptr++;
3232                         if (*ptr == '\0') return 1;
3233                         while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.') { 
3234                                 ptr++;
3235                         }
3236                         if (*ptr == '\0') return 1;
3237                         if (*ptr == 'l') {
3238                                 ptr++;
3239                                 n++;
3240                                 if (*ptr == '\0') return 1;
3241                                 if (*ptr == 'e' || *ptr == 'f') { 
3242                                         ptr++; 
3243                                         } else { return 1; }
3244                         }
3245                         else if (*ptr == 's' || *ptr == 'S' || *ptr == '%') { ++ptr; }
3246                         else { return 1; }
3247                 } else {
3248                         ++ptr;
3249                 }
3250         }
3251         return (n!=1);
3252 }
3253 int
3254 vdef_parse(gdes,str)
3255 struct graph_desc_t *gdes;
3256 char *str;
3257 {
3258     /* A VDEF currently is either "func" or "param,func"
3259      * so the parsing is rather simple.  Change if needed.
3260      */
3261     double      param;
3262     char        func[30];
3263     int         n;
3264     
3265     n=0;
3266     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3267     if (n==strlen(str)) { /* matched */
3268         ;
3269     } else {
3270         n=0;
3271         sscanf(str,"%29[A-Z]%n",func,&n);
3272         if (n==strlen(str)) { /* matched */
3273             param=DNAN;
3274         } else {
3275             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3276                 ,str
3277                 ,gdes->vname
3278                 );
3279             return -1;
3280         }
3281     }
3282     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3283     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3284     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3285     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3286     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3287     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3288     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3289     else {
3290         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3291             ,func
3292             ,gdes->vname
3293             );
3294         return -1;
3295     };
3296
3297     switch (gdes->vf.op) {
3298         case VDEF_PERCENT:
3299             if (isnan(param)) { /* no parameter given */
3300                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3301                     ,func
3302                     ,gdes->vname
3303                     );
3304                 return -1;
3305             };
3306             if (param>=0.0 && param<=100.0) {
3307                 gdes->vf.param = param;
3308                 gdes->vf.val   = DNAN;  /* undefined */
3309                 gdes->vf.when  = 0;     /* undefined */
3310             } else {
3311                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3312                     ,param
3313                     ,gdes->vname
3314                     );
3315                 return -1;
3316             };
3317             break;
3318         case VDEF_MAXIMUM:
3319         case VDEF_AVERAGE:
3320         case VDEF_MINIMUM:
3321         case VDEF_TOTAL:
3322         case VDEF_FIRST:
3323         case VDEF_LAST:
3324             if (isnan(param)) {
3325                 gdes->vf.param = DNAN;
3326                 gdes->vf.val   = DNAN;
3327                 gdes->vf.when  = 0;
3328             } else {
3329                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3330                     ,func
3331                     ,gdes->vname
3332                     );
3333                 return -1;
3334             };
3335             break;
3336     };
3337     return 0;
3338 }
3339 int
3340 vdef_calc(im,gdi)
3341 image_desc_t *im;
3342 int gdi;
3343 {
3344     graph_desc_t        *src,*dst;
3345     rrd_value_t         *data;
3346     long                step,steps;
3347
3348     dst = &im->gdes[gdi];
3349     src = &im->gdes[dst->vidx];
3350     data = src->data + src->ds;
3351     steps = (src->end - src->start) / src->step;
3352
3353 #if 0
3354 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3355     ,src->start
3356     ,src->end
3357     ,steps
3358     );
3359 #endif
3360
3361     switch (dst->vf.op) {
3362         case VDEF_PERCENT: {
3363                 rrd_value_t *   array;
3364                 int             field;
3365
3366
3367                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3368                     rrd_set_error("malloc VDEV_PERCENT");
3369                     return -1;
3370                 }
3371                 for (step=0;step < steps; step++) {
3372                     array[step]=data[step*src->ds_cnt];
3373                 }
3374                 qsort(array,step,sizeof(double),vdef_percent_compar);
3375
3376                 field = (steps-1)*dst->vf.param/100;
3377                 dst->vf.val  = array[field];
3378                 dst->vf.when = 0;       /* no time component */
3379 #if 0
3380 for(step=0;step<steps;step++)
3381 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3382 #endif
3383             }
3384             break;
3385         case VDEF_MAXIMUM:
3386             step=0;
3387             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3388             if (step == steps) {
3389                 dst->vf.val  = DNAN;
3390                 dst->vf.when = 0;
3391             } else {
3392                 dst->vf.val  = data[step*src->ds_cnt];
3393                 dst->vf.when = src->start + (step+1)*src->step;
3394             }
3395             while (step != steps) {
3396                 if (finite(data[step*src->ds_cnt])) {
3397                     if (data[step*src->ds_cnt] > dst->vf.val) {
3398                         dst->vf.val  = data[step*src->ds_cnt];
3399                         dst->vf.when = src->start + (step+1)*src->step;
3400                     }
3401                 }
3402                 step++;
3403             }
3404             break;
3405         case VDEF_TOTAL:
3406         case VDEF_AVERAGE: {
3407             int cnt=0;
3408             double sum=0.0;
3409             for (step=0;step<steps;step++) {
3410                 if (finite(data[step*src->ds_cnt])) {
3411                     sum += data[step*src->ds_cnt];
3412                     cnt ++;
3413                 };
3414             }
3415             if (cnt) {
3416                 if (dst->vf.op == VDEF_TOTAL) {
3417                     dst->vf.val  = sum*src->step;
3418                     dst->vf.when = cnt*src->step;       /* not really "when" */
3419                 } else {
3420                     dst->vf.val = sum/cnt;
3421                     dst->vf.when = 0;   /* no time component */
3422                 };
3423             } else {
3424                 dst->vf.val  = DNAN;
3425                 dst->vf.when = 0;
3426             }
3427             }
3428             break;
3429         case VDEF_MINIMUM:
3430             step=0;
3431             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3432             if (step == steps) {
3433                 dst->vf.val  = DNAN;
3434                 dst->vf.when = 0;
3435             } else {
3436                 dst->vf.val  = data[step*src->ds_cnt];
3437                 dst->vf.when = src->start + (step+1)*src->step;
3438             }
3439             while (step != steps) {
3440                 if (finite(data[step*src->ds_cnt])) {
3441                     if (data[step*src->ds_cnt] < dst->vf.val) {
3442                         dst->vf.val  = data[step*src->ds_cnt];
3443                         dst->vf.when = src->start + (step+1)*src->step;
3444                     }
3445                 }
3446                 step++;
3447             }
3448             break;
3449         case VDEF_FIRST:
3450             /* The time value returned here is one step before the
3451              * actual time value.  This is the start of the first
3452              * non-NaN interval.
3453              */
3454             step=0;
3455             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3456             if (step == steps) { /* all entries were NaN */
3457                 dst->vf.val  = DNAN;
3458                 dst->vf.when = 0;
3459             } else {
3460                 dst->vf.val  = data[step*src->ds_cnt];
3461                 dst->vf.when = src->start + step*src->step;
3462             }
3463             break;
3464         case VDEF_LAST:
3465             /* The time value returned here is the
3466              * actual time value.  This is the end of the last
3467              * non-NaN interval.
3468              */
3469             step=steps-1;
3470             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3471             if (step < 0) { /* all entries were NaN */
3472                 dst->vf.val  = DNAN;
3473                 dst->vf.when = 0;
3474             } else {
3475                 dst->vf.val  = data[step*src->ds_cnt];
3476                 dst->vf.when = src->start + (step+1)*src->step;
3477             }
3478             break;
3479     }
3480     return 0;
3481 }
3482
3483 /* NaN < -INF < finite_values < INF */
3484 int
3485 vdef_percent_compar(a,b)
3486 const void *a,*b;
3487 {
3488     /* Equality is not returned; this doesn't hurt except
3489      * (maybe) for a little performance.
3490      */
3491
3492     /* First catch NaN values. They are smallest */
3493     if (isnan( *(double *)a )) return -1;
3494     if (isnan( *(double *)b )) return  1;
3495
3496     /* NaN doesn't reach this part so INF and -INF are extremes.
3497      * The sign from isinf() is compatible with the sign we return
3498      */
3499     if (isinf( *(double *)a )) return isinf( *(double *)a );
3500     if (isinf( *(double *)b )) return isinf( *(double *)b );
3501
3502     /* If we reach this, both values must be finite */
3503     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3504 }