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