DEF now takes "--start" and "--end"
[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++) { /* for each pixel */
995         long vidx;
996         gr_time = im->start+pixstep*i; /* time of the current step */
997         paintval=0.0;
998         
999         for (ii=0;ii<im->gdes_c;ii++) {
1000             double value;
1001             switch (im->gdes[ii].gf) {
1002                 case GF_LINE:
1003                 case GF_AREA:
1004                 case GF_TICK:
1005                     if (!im->gdes[ii].stack)
1006                         paintval = 0.0;
1007                 case GF_STACK:
1008                     /* The time of the data doesn't necessarily match
1009                     ** the time of the graph. Beware.
1010                     */
1011                     vidx = im->gdes[ii].vidx;
1012                     if (        (gr_time >= im->gdes[vidx].start) &&
1013                                 (gr_time <= im->gdes[vidx].end) ) {
1014                         value = im->gdes[vidx].data[
1015                             (unsigned long) floor(
1016                                     (double)(gr_time - im->gdes[vidx].start)
1017                                                 / im->gdes[vidx].step)
1018                             * im->gdes[vidx].ds_cnt
1019                             + im->gdes[vidx].ds
1020                         ];
1021                     } else {
1022                         value = DNAN;
1023                     }
1024
1025                     if (! isnan(value)) {
1026                         paintval += value;
1027                         im->gdes[ii].p_data[i] = paintval;
1028                         /* GF_TICK: the data values are not
1029                         ** relevant for min and max
1030                         */
1031                         if (finite(paintval) && im->gdes[ii].gf != GF_TICK ) {
1032                             if (isnan(minval) || paintval <  minval)
1033                                 minval = paintval;
1034                             if (isnan(maxval) || paintval >  maxval)
1035                                 maxval = paintval;
1036                         }
1037                     } else {
1038                         im->gdes[ii].p_data[i] = DNAN;
1039                     }
1040                     break;
1041                 default:
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         /* don't adjust low-end with log scale */
1057         || ((!im->logarithmic && !im->rigid) && im->minval > minval)
1058         )
1059         im->minval = minval;
1060     if (isnan(im->maxval) 
1061         || (!im->rigid && im->maxval < maxval)
1062         ) {
1063         if (im->logarithmic)
1064             im->maxval = maxval * 1.1;
1065         else
1066             im->maxval = maxval;
1067     }
1068     /* make sure min and max are not equal */
1069     if (im->minval == im->maxval) {
1070         im->maxval *= 1.01; 
1071         if (! im->logarithmic) {
1072             im->minval *= 0.99;
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     return 0;
1080 }
1081
1082
1083
1084 /* identify the point where the first gridline, label ... gets placed */
1085
1086 time_t
1087 find_first_time(
1088     time_t   start, /* what is the initial time */
1089     enum tmt_en baseint,  /* what is the basic interval */
1090     long     basestep /* how many if these do we jump a time */
1091     )
1092 {
1093     struct tm tm;
1094     tm = *localtime(&start);
1095     switch(baseint){
1096     case TMT_SECOND:
1097         tm.tm_sec -= tm.tm_sec % basestep; break;
1098     case TMT_MINUTE: 
1099         tm.tm_sec=0;
1100         tm.tm_min -= tm.tm_min % basestep; 
1101         break;
1102     case TMT_HOUR:
1103         tm.tm_sec=0;
1104         tm.tm_min = 0;
1105         tm.tm_hour -= tm.tm_hour % basestep; break;
1106     case TMT_DAY:
1107         /* we do NOT look at the basestep for this ... */
1108         tm.tm_sec=0;
1109         tm.tm_min = 0;
1110         tm.tm_hour = 0; break;
1111     case TMT_WEEK:
1112         /* we do NOT look at the basestep for this ... */
1113         tm.tm_sec=0;
1114         tm.tm_min = 0;
1115         tm.tm_hour = 0;
1116         tm.tm_mday -= tm.tm_wday -1;    /* -1 because we want the monday */
1117         if (tm.tm_wday==0) tm.tm_mday -= 7; /* we want the *previous* monday */
1118         break;
1119     case TMT_MONTH:
1120         tm.tm_sec=0;
1121         tm.tm_min = 0;
1122         tm.tm_hour = 0;
1123         tm.tm_mday = 1;
1124         tm.tm_mon -= tm.tm_mon % basestep; break;
1125
1126     case TMT_YEAR:
1127         tm.tm_sec=0;
1128         tm.tm_min = 0;
1129         tm.tm_hour = 0;
1130         tm.tm_mday = 1;
1131         tm.tm_mon = 0;
1132         tm.tm_year -= (tm.tm_year+1900) % basestep;
1133         
1134     }
1135     return mktime(&tm);
1136 }
1137 /* identify the point where the next gridline, label ... gets placed */
1138 time_t 
1139 find_next_time(
1140     time_t   current, /* what is the initial time */
1141     enum tmt_en baseint,  /* what is the basic interval */
1142     long     basestep /* how many if these do we jump a time */
1143     )
1144 {
1145     struct tm tm;
1146     time_t madetime;
1147     tm = *localtime(&current);
1148     do {
1149         switch(baseint){
1150         case TMT_SECOND:
1151             tm.tm_sec += basestep; break;
1152         case TMT_MINUTE: 
1153             tm.tm_min += basestep; break;
1154         case TMT_HOUR:
1155             tm.tm_hour += basestep; break;
1156         case TMT_DAY:
1157             tm.tm_mday += basestep; break;
1158         case TMT_WEEK:
1159             tm.tm_mday += 7*basestep; break;
1160         case TMT_MONTH:
1161             tm.tm_mon += basestep; break;
1162         case TMT_YEAR:
1163             tm.tm_year += basestep;     
1164         }
1165         madetime = mktime(&tm);
1166     } while (madetime == -1); /* this is necessary to skip impssible times
1167                                  like the daylight saving time skips */
1168     return madetime;
1169           
1170 }
1171
1172
1173 /* calculate values required for PRINT and GPRINT functions */
1174
1175 int
1176 print_calc(image_desc_t *im, char ***prdata) 
1177 {
1178     long i,ii,validsteps;
1179     double printval;
1180     time_t printtime;
1181     int graphelement = 0;
1182     long vidx;
1183     int max_ii; 
1184     double magfact = -1;
1185     char *si_symb = "";
1186     char *percent_s;
1187     int prlines = 1;
1188     if (im->imginfo) prlines++;
1189     for(i=0;i<im->gdes_c;i++){
1190         switch(im->gdes[i].gf){
1191         case GF_PRINT:
1192             prlines++;
1193             if(((*prdata) = rrd_realloc((*prdata),prlines*sizeof(char *)))==NULL){
1194                 rrd_set_error("realloc prdata");
1195                 return 0;
1196             }
1197         case GF_GPRINT:
1198             /* PRINT and GPRINT can now print VDEF generated values.
1199              * There's no need to do any calculations on them as these
1200              * calculations were already made.
1201              */
1202             vidx = im->gdes[i].vidx;
1203             if (im->gdes[vidx].gf==GF_VDEF) { /* simply use vals */
1204                 printval = im->gdes[vidx].vf.val;
1205                 printtime = im->gdes[vidx].vf.when;
1206             } else { /* need to calculate max,min,avg etcetera */
1207                 max_ii =((im->gdes[vidx].end 
1208                         - im->gdes[vidx].start)
1209                         / im->gdes[vidx].step
1210                         * im->gdes[vidx].ds_cnt);
1211                 printval = DNAN;
1212                 validsteps = 0;
1213                 for(    ii=im->gdes[vidx].ds;
1214                         ii < max_ii;
1215                         ii+=im->gdes[vidx].ds_cnt){
1216                     if (! finite(im->gdes[vidx].data[ii]))
1217                         continue;
1218                     if (isnan(printval)){
1219                         printval = im->gdes[vidx].data[ii];
1220                         validsteps++;
1221                         continue;
1222                     }
1223
1224                     switch (im->gdes[i].cf){
1225                         case CF_HWPREDICT:
1226                         case CF_DEVPREDICT:
1227                         case CF_DEVSEASONAL:
1228                         case CF_SEASONAL:
1229                         case CF_AVERAGE:
1230                             validsteps++;
1231                             printval += im->gdes[vidx].data[ii];
1232                             break;
1233                         case CF_MINIMUM:
1234                             printval = min( printval, im->gdes[vidx].data[ii]);
1235                             break;
1236                         case CF_FAILURES:
1237                         case CF_MAXIMUM:
1238                             printval = max( printval, im->gdes[vidx].data[ii]);
1239                             break;
1240                         case CF_LAST:
1241                             printval = im->gdes[vidx].data[ii];
1242                     }
1243                 }
1244                 if (im->gdes[i].cf==CF_AVERAGE || im->gdes[i].cf > CF_LAST) {
1245                     if (validsteps > 1) {
1246                         printval = (printval / validsteps);
1247                     }
1248                 }
1249             } /* prepare printval */
1250
1251             if (!strcmp(im->gdes[i].format,"%c")) { /* VDEF time print */
1252                 if (im->gdes[i].gf == GF_PRINT){
1253                     (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1254                     sprintf((*prdata)[prlines-2],"%s (%lu)",
1255                                         ctime(&printtime),printtime);
1256                     (*prdata)[prlines-1] = NULL;
1257                 } else {
1258                     sprintf(im->gdes[i].legend,"%s (%lu)",
1259                                         ctime(&printtime),printtime);
1260                     graphelement = 1;
1261                 }
1262             } else {
1263             if ((percent_s = strstr(im->gdes[i].format,"%S")) != NULL) {
1264                 /* Magfact is set to -1 upon entry to print_calc.  If it
1265                  * is still less than 0, then we need to run auto_scale.
1266                  * Otherwise, put the value into the correct units.  If
1267                  * the value is 0, then do not set the symbol or magnification
1268                  * so next the calculation will be performed again. */
1269                 if (magfact < 0.0) {
1270                     auto_scale(im,&printval,&si_symb,&magfact);
1271                     if (printval == 0.0)
1272                         magfact = -1.0;
1273                 } else {
1274                     printval /= magfact;
1275                 }
1276                 *(++percent_s) = 's';
1277             } else if (strstr(im->gdes[i].format,"%s") != NULL) {
1278                 auto_scale(im,&printval,&si_symb,&magfact);
1279             }
1280
1281             if (im->gdes[i].gf == GF_PRINT){
1282                 (*prdata)[prlines-2] = malloc((FMT_LEG_LEN+2)*sizeof(char));
1283                 if (bad_format(im->gdes[i].format)) {
1284                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1285                         return -1;
1286                 }
1287 #ifdef HAVE_SNPRINTF
1288                 snprintf((*prdata)[prlines-2],FMT_LEG_LEN,im->gdes[i].format,printval,si_symb);
1289 #else
1290                 sprintf((*prdata)[prlines-2],im->gdes[i].format,printval,si_symb);
1291 #endif
1292                 (*prdata)[prlines-1] = NULL;
1293             } else {
1294                 /* GF_GPRINT */
1295
1296                 if (bad_format(im->gdes[i].format)) {
1297                         rrd_set_error("bad format for [G]PRINT in '%s'", im->gdes[i].format);
1298                         return -1;
1299                 }
1300 #ifdef HAVE_SNPRINTF
1301                 snprintf(im->gdes[i].legend,FMT_LEG_LEN-2,im->gdes[i].format,printval,si_symb);
1302 #else
1303                 sprintf(im->gdes[i].legend,im->gdes[i].format,printval,si_symb);
1304 #endif
1305                 graphelement = 1;
1306             }
1307             }
1308             break;
1309         case GF_LINE:
1310         case GF_AREA:
1311         case GF_TICK:
1312         case GF_STACK:
1313         case GF_HRULE:
1314         case GF_VRULE:
1315             graphelement = 1;
1316             break;
1317         case GF_COMMENT:
1318         case GF_DEF:
1319         case GF_CDEF:       
1320         case GF_VDEF:       
1321         case GF_PART:
1322         case GF_XPORT:
1323             break;
1324         }
1325     }
1326     return graphelement;
1327 }
1328
1329
1330 /* place legends with color spots */
1331 int
1332 leg_place(image_desc_t *im)
1333 {
1334     /* graph labels */
1335     int   interleg = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1336     int   box =im->text_prop[TEXT_PROP_LEGEND].size*1.5;
1337     int   border = im->text_prop[TEXT_PROP_LEGEND].size*2.0;
1338     int   fill=0, fill_last;
1339     int   leg_c = 0;
1340     int   leg_x = border, leg_y = im->yimg;
1341     int   leg_cc;
1342     int   glue = 0;
1343     int   i,ii, mark = 0;
1344     char  prt_fctn; /*special printfunctions */
1345     int  *legspace;
1346
1347   if( !(im->extra_flags & NOLEGEND) ) {
1348     if ((legspace = malloc(im->gdes_c*sizeof(int)))==NULL){
1349        rrd_set_error("malloc for legspace");
1350        return -1;
1351     }
1352
1353     for(i=0;i<im->gdes_c;i++){
1354         fill_last = fill;
1355
1356         leg_cc = strlen(im->gdes[i].legend);
1357         
1358         /* is there a controle code ant the end of the legend string ? */ 
1359         if (leg_cc >= 2 && im->gdes[i].legend[leg_cc-2] == '\\') {
1360             prt_fctn = im->gdes[i].legend[leg_cc-1];
1361             leg_cc -= 2;
1362             im->gdes[i].legend[leg_cc] = '\0';
1363         } else {
1364             prt_fctn = '\0';
1365         }
1366         /* remove exess space */
1367         while (prt_fctn=='g' && 
1368                leg_cc > 0 && 
1369                im->gdes[i].legend[leg_cc-1]==' '){
1370            leg_cc--;
1371            im->gdes[i].legend[leg_cc]='\0';
1372         }
1373         if (leg_cc != 0 ){
1374            legspace[i]=(prt_fctn=='g' ? 0 : interleg);
1375            
1376            if (fill > 0){ 
1377                /* no interleg space if string ends in \g */
1378                fill += legspace[i];
1379             }
1380             if (im->gdes[i].gf != GF_GPRINT && 
1381                 im->gdes[i].gf != GF_COMMENT) { 
1382                 fill += box;       
1383             }
1384            fill += gfx_get_text_width(im->canvas, fill+border,
1385                                       im->text_prop[TEXT_PROP_LEGEND].font,
1386                                       im->text_prop[TEXT_PROP_LEGEND].size,
1387                                       im->tabwidth,
1388                                       im->gdes[i].legend);
1389             leg_c++;
1390         } else {
1391            legspace[i]=0;
1392         }
1393         /* who said there was a special tag ... ?*/
1394         if (prt_fctn=='g') {    
1395            prt_fctn = '\0';
1396         }
1397         if (prt_fctn == '\0') {
1398             if (i == im->gdes_c -1 ) prt_fctn ='l';
1399             
1400             /* is it time to place the legends ? */
1401             if (fill > im->ximg - 2*border){
1402                 if (leg_c > 1) {
1403                     /* go back one */
1404                     i--; 
1405                     fill = fill_last;
1406                     leg_c--;
1407                     prt_fctn = 'j';
1408                 } else {
1409                     prt_fctn = 'l';
1410                 }
1411                 
1412             }
1413         }
1414
1415
1416         if (prt_fctn != '\0'){
1417             leg_x = border;
1418             if (leg_c >= 2 && prt_fctn == 'j') {
1419                 glue = (im->ximg - fill - 2* border) / (leg_c-1);
1420             } else {
1421                 glue = 0;
1422             }
1423             if (prt_fctn =='c') leg_x =  (im->ximg - fill) / 2.0;
1424             if (prt_fctn =='r') leg_x =  im->ximg - fill - border;
1425
1426             for(ii=mark;ii<=i;ii++){
1427                 if(im->gdes[ii].legend[0]=='\0')
1428                     continue;
1429                 im->gdes[ii].leg_x = leg_x;
1430                 im->gdes[ii].leg_y = leg_y;
1431                 leg_x += 
1432                  gfx_get_text_width(im->canvas, leg_x,
1433                                       im->text_prop[TEXT_PROP_LEGEND].font,
1434                                       im->text_prop[TEXT_PROP_LEGEND].size,
1435                                       im->tabwidth,
1436                                       im->gdes[ii].legend) 
1437                    + legspace[ii]
1438                    + glue;
1439                 if (im->gdes[ii].gf != GF_GPRINT && 
1440                     im->gdes[ii].gf != GF_COMMENT) 
1441                     leg_x += box;          
1442             }       
1443             leg_y = leg_y + im->text_prop[TEXT_PROP_LEGEND].size*1.2;
1444             if (prt_fctn == 's') leg_y -=  im->text_prop[TEXT_PROP_LEGEND].size*1.2;       
1445             fill = 0;
1446             leg_c = 0;
1447             mark = ii;
1448         }          
1449     }
1450     im->yimg = leg_y;
1451     free(legspace);
1452   }
1453   return 0;
1454 }
1455
1456 /* create a grid on the graph. it determines what to do
1457    from the values of xsize, start and end */
1458
1459 /* the xaxis labels are determined from the number of seconds per pixel
1460    in the requested graph */
1461
1462
1463
1464 int
1465 calc_horizontal_grid(image_desc_t   *im)
1466 {
1467     double   range;
1468     double   scaledrange;
1469     int      pixel,i;
1470     int      gridind;
1471     int      decimals, fractionals;
1472
1473     im->ygrid_scale.labfact=2;
1474     gridind=-1;
1475     range =  im->maxval - im->minval;
1476     scaledrange = range / im->magfact;
1477
1478         /* does the scale of this graph make it impossible to put lines
1479            on it? If so, give up. */
1480         if (isnan(scaledrange)) {
1481                 return 0;
1482         }
1483
1484     /* find grid spaceing */
1485     pixel=1;
1486     if(isnan(im->ygridstep)){
1487         if(im->extra_flags & ALTYGRID) {
1488             /* find the value with max number of digits. Get number of digits */
1489             decimals = ceil(log10(max(fabs(im->maxval), fabs(im->minval))));
1490             if(decimals <= 0) /* everything is small. make place for zero */
1491                 decimals = 1;
1492             
1493             fractionals = floor(log10(range));
1494             if(fractionals < 0) /* small amplitude. */
1495                 sprintf(im->ygrid_scale.labfmt, "%%%d.%df", decimals - fractionals + 1, -fractionals + 1);
1496             else
1497                 sprintf(im->ygrid_scale.labfmt, "%%%d.1f", decimals + 1);
1498             im->ygrid_scale.gridstep = pow((double)10, (double)fractionals);
1499             if(im->ygrid_scale.gridstep == 0) /* range is one -> 0.1 is reasonable scale */
1500                 im->ygrid_scale.gridstep = 0.1;
1501             /* should have at least 5 lines but no more then 15 */
1502             if(range/im->ygrid_scale.gridstep < 5)
1503                 im->ygrid_scale.gridstep /= 10;
1504             if(range/im->ygrid_scale.gridstep > 15)
1505                 im->ygrid_scale.gridstep *= 10;
1506             if(range/im->ygrid_scale.gridstep > 5) {
1507                 im->ygrid_scale.labfact = 1;
1508                 if(range/im->ygrid_scale.gridstep > 8)
1509                     im->ygrid_scale.labfact = 2;
1510             }
1511             else {
1512                 im->ygrid_scale.gridstep /= 5;
1513                 im->ygrid_scale.labfact = 5;
1514             }
1515         }
1516         else {
1517             for(i=0;ylab[i].grid > 0;i++){
1518                 pixel = im->ysize / (scaledrange / ylab[i].grid);
1519                 if (gridind == -1 && pixel > 5) {
1520                     gridind = i;
1521                     break;
1522                 }
1523             }
1524             
1525             for(i=0; i<4;i++) {
1526                if (pixel * ylab[gridind].lfac[i] >=  2 * im->text_prop[TEXT_PROP_AXIS].size) {
1527                   im->ygrid_scale.labfact =  ylab[gridind].lfac[i];
1528                   break;
1529                }                          
1530             } 
1531             
1532             im->ygrid_scale.gridstep = ylab[gridind].grid * im->magfact;
1533         }
1534     } else {
1535         im->ygrid_scale.gridstep = im->ygridstep;
1536         im->ygrid_scale.labfact = im->ylabfact;
1537     }
1538     return 1;
1539 }
1540
1541 int draw_horizontal_grid(image_desc_t *im)
1542 {
1543     int      i;
1544     double   scaledstep;
1545     char     graph_label[100];
1546     double X0=im->xorigin;
1547     double X1=im->xorigin+im->xsize;
1548    
1549     int sgrid = (int)( im->minval / im->ygrid_scale.gridstep - 1);
1550     int egrid = (int)( im->maxval / im->ygrid_scale.gridstep + 1);
1551     scaledstep = im->ygrid_scale.gridstep/im->magfact;
1552     for (i = sgrid; i <= egrid; i++){
1553        double Y0=ytr(im,im->ygrid_scale.gridstep*i);
1554        if ( Y0 >= im->yorigin-im->ysize
1555                  && Y0 <= im->yorigin){       
1556             if(i % im->ygrid_scale.labfact == 0){               
1557                 if (i==0 || im->symbol == ' ') {
1558                     if(scaledstep < 1){
1559                         if(im->extra_flags & ALTYGRID) {
1560                             sprintf(graph_label,im->ygrid_scale.labfmt,scaledstep*i);
1561                         }
1562                         else {
1563                             sprintf(graph_label,"%4.1f",scaledstep*i);
1564                         }
1565                     } else {
1566                         sprintf(graph_label,"%4.0f",scaledstep*i);
1567                     }
1568                 }else {
1569                     if(scaledstep < 1){
1570                         sprintf(graph_label,"%4.1f %c",scaledstep*i, im->symbol);
1571                     } else {
1572                         sprintf(graph_label,"%4.0f %c",scaledstep*i, im->symbol);
1573                     }
1574                 }
1575
1576                gfx_new_text ( im->canvas,
1577                               X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1578                               im->graph_col[GRC_FONT],
1579                               im->text_prop[TEXT_PROP_AXIS].font,
1580                               im->text_prop[TEXT_PROP_AXIS].size,
1581                               im->tabwidth, 0.0, GFX_H_RIGHT, GFX_V_CENTER,
1582                               graph_label );
1583                gfx_new_dashed_line ( im->canvas,
1584                               X0-2,Y0,
1585                               X1+2,Y0,
1586                               MGRIDWIDTH, im->graph_col[GRC_MGRID],
1587                               im->grid_dash_on, im->grid_dash_off);            
1588                
1589             } else {            
1590                gfx_new_dashed_line ( im->canvas,
1591                               X0-1,Y0,
1592                               X1+1,Y0,
1593                               GRIDWIDTH, im->graph_col[GRC_GRID],
1594                               im->grid_dash_on, im->grid_dash_off);            
1595                
1596             }       
1597         }       
1598     } 
1599     return 1;
1600 }
1601
1602 /* logaritmic horizontal grid */
1603 int
1604 horizontal_log_grid(image_desc_t   *im)   
1605 {
1606     double   pixpex;
1607     int      ii,i;
1608     int      minoridx=0, majoridx=0;
1609     char     graph_label[100];
1610     double   X0,X1,Y0;   
1611     double   value, pixperstep, minstep;
1612
1613     /* find grid spaceing */
1614     pixpex= (double)im->ysize / (log10(im->maxval) - log10(im->minval));
1615
1616         if (isnan(pixpex)) {
1617                 return 0;
1618         }
1619
1620     for(i=0;yloglab[i][0] > 0;i++){
1621         minstep = log10(yloglab[i][0]);
1622         for(ii=1;yloglab[i][ii+1] > 0;ii++){
1623             if(yloglab[i][ii+2]==0){
1624                 minstep = log10(yloglab[i][ii+1])-log10(yloglab[i][ii]);
1625                 break;
1626             }
1627         }
1628         pixperstep = pixpex * minstep;
1629         if(pixperstep > 5){minoridx = i;}
1630        if(pixperstep > 2 *  im->text_prop[TEXT_PROP_LEGEND].size){majoridx = i;}
1631     }
1632    
1633    X0=im->xorigin;
1634    X1=im->xorigin+im->xsize;
1635     /* paint minor grid */
1636     for (value = pow((double)10, log10(im->minval) 
1637                           - fmod(log10(im->minval),log10(yloglab[minoridx][0])));
1638          value  <= im->maxval;
1639          value *= yloglab[minoridx][0]){
1640         if (value < im->minval) continue;
1641         i=0;    
1642         while(yloglab[minoridx][++i] > 0){          
1643            Y0 = ytr(im,value * yloglab[minoridx][i]);
1644            if (Y0 <= im->yorigin - im->ysize) break;
1645            gfx_new_dashed_line ( im->canvas,
1646                           X0-1,Y0,
1647                           X1+1,Y0,
1648                           GRIDWIDTH, im->graph_col[GRC_GRID],
1649                           im->grid_dash_on, im->grid_dash_off);
1650         }
1651     }
1652
1653     /* paint major grid and labels*/
1654     for (value = pow((double)10, log10(im->minval) 
1655                           - fmod(log10(im->minval),log10(yloglab[majoridx][0])));
1656          value <= im->maxval;
1657          value *= yloglab[majoridx][0]){
1658         if (value < im->minval) continue;
1659         i=0;    
1660         while(yloglab[majoridx][++i] > 0){          
1661            Y0 = ytr(im,value * yloglab[majoridx][i]);    
1662            if (Y0 <= im->yorigin - im->ysize) break;
1663            gfx_new_dashed_line ( im->canvas,
1664                           X0-2,Y0,
1665                           X1+2,Y0,
1666                           MGRIDWIDTH, im->graph_col[GRC_MGRID],
1667                           im->grid_dash_on, im->grid_dash_off);
1668            
1669            sprintf(graph_label,"%3.0e",value * yloglab[majoridx][i]);
1670            gfx_new_text ( im->canvas,
1671                           X0-im->text_prop[TEXT_PROP_AXIS].size/1.5, Y0,
1672                           im->graph_col[GRC_FONT],
1673                           im->text_prop[TEXT_PROP_AXIS].font,
1674                           im->text_prop[TEXT_PROP_AXIS].size,
1675                           im->tabwidth,0.0, GFX_H_RIGHT, GFX_V_CENTER,
1676                           graph_label );
1677         } 
1678     }
1679         return 1;
1680 }
1681
1682
1683 void
1684 vertical_grid(
1685     image_desc_t   *im )
1686 {   
1687     int xlab_sel;               /* which sort of label and grid ? */
1688     time_t ti, tilab, timajor;
1689     long factor;
1690     char graph_label[100];
1691     double X0,Y0,Y1; /* points for filled graph and more*/
1692    
1693
1694     /* the type of time grid is determined by finding
1695        the number of seconds per pixel in the graph */
1696     
1697     
1698     if(im->xlab_user.minsec == -1){
1699         factor=(im->end - im->start)/im->xsize;
1700         xlab_sel=0;
1701         while ( xlab[xlab_sel+1].minsec != -1 
1702                 && xlab[xlab_sel+1].minsec <= factor){ xlab_sel++; }
1703         im->xlab_user.gridtm = xlab[xlab_sel].gridtm;
1704         im->xlab_user.gridst = xlab[xlab_sel].gridst;
1705         im->xlab_user.mgridtm = xlab[xlab_sel].mgridtm;
1706         im->xlab_user.mgridst = xlab[xlab_sel].mgridst;
1707         im->xlab_user.labtm = xlab[xlab_sel].labtm;
1708         im->xlab_user.labst = xlab[xlab_sel].labst;
1709         im->xlab_user.precis = xlab[xlab_sel].precis;
1710         im->xlab_user.stst = xlab[xlab_sel].stst;
1711     }
1712     
1713     /* y coords are the same for every line ... */
1714     Y0 = im->yorigin;
1715     Y1 = im->yorigin-im->ysize;
1716    
1717
1718     /* paint the minor grid */
1719     for(ti = find_first_time(im->start,
1720                             im->xlab_user.gridtm,
1721                             im->xlab_user.gridst),
1722         timajor = find_first_time(im->start,
1723                             im->xlab_user.mgridtm,
1724                             im->xlab_user.mgridst);
1725         ti < im->end; 
1726         ti = find_next_time(ti,im->xlab_user.gridtm,im->xlab_user.gridst)
1727         ){
1728         /* are we inside the graph ? */
1729         if (ti < im->start || ti > im->end) continue;
1730         while (timajor < ti) {
1731             timajor = find_next_time(timajor,
1732                     im->xlab_user.mgridtm, im->xlab_user.mgridst);
1733         }
1734         if (ti == timajor) continue; /* skip as falls on major grid line */
1735        X0 = xtr(im,ti);       
1736        gfx_new_dashed_line(im->canvas,X0,Y0+1, X0,Y1-1,GRIDWIDTH,
1737            im->graph_col[GRC_GRID],
1738            im->grid_dash_on, im->grid_dash_off);
1739        
1740     }
1741
1742     /* paint the major grid */
1743     for(ti = find_first_time(im->start,
1744                             im->xlab_user.mgridtm,
1745                             im->xlab_user.mgridst);
1746         ti < im->end; 
1747         ti = find_next_time(ti,im->xlab_user.mgridtm,im->xlab_user.mgridst)
1748         ){
1749         /* are we inside the graph ? */
1750         if (ti < im->start || ti > im->end) continue;
1751        X0 = xtr(im,ti);
1752        gfx_new_dashed_line(im->canvas,X0,Y0+3, X0,Y1-2,MGRIDWIDTH,
1753            im->graph_col[GRC_MGRID],
1754            im->grid_dash_on, im->grid_dash_off);
1755        
1756     }
1757     /* paint the labels below the graph */
1758     for(ti = find_first_time(im->start,
1759                             im->xlab_user.labtm,
1760                             im->xlab_user.labst);
1761         ti <= im->end; 
1762         ti = find_next_time(ti,im->xlab_user.labtm,im->xlab_user.labst)
1763         ){
1764         tilab= ti + im->xlab_user.precis/2; /* correct time for the label */
1765         /* are we inside the graph ? */
1766         if (ti < im->start || ti > im->end) continue;
1767
1768 #if HAVE_STRFTIME
1769         strftime(graph_label,99,im->xlab_user.stst,localtime(&tilab));
1770 #else
1771 # error "your libc has no strftime I guess we'll abort the exercise here."
1772 #endif
1773        gfx_new_text ( im->canvas,
1774                       xtr(im,tilab), Y0+im->text_prop[TEXT_PROP_AXIS].size/1.5,
1775                       im->graph_col[GRC_FONT],
1776                       im->text_prop[TEXT_PROP_AXIS].font,
1777                       im->text_prop[TEXT_PROP_AXIS].size,
1778                       im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_TOP,
1779                       graph_label );
1780        
1781     }
1782
1783 }
1784
1785
1786 void 
1787 axis_paint(
1788    image_desc_t   *im
1789            )
1790 {   
1791     /* draw x and y axis */
1792     gfx_new_line ( im->canvas, im->xorigin+im->xsize,im->yorigin,
1793                       im->xorigin+im->xsize,im->yorigin-im->ysize,
1794                       GRIDWIDTH, im->graph_col[GRC_GRID]);
1795        
1796        gfx_new_line ( im->canvas, im->xorigin,im->yorigin-im->ysize,
1797                          im->xorigin+im->xsize,im->yorigin-im->ysize,
1798                          GRIDWIDTH, im->graph_col[GRC_GRID]);
1799    
1800        gfx_new_line ( im->canvas, im->xorigin-4,im->yorigin,
1801                          im->xorigin+im->xsize+4,im->yorigin,
1802                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1803    
1804        gfx_new_line ( im->canvas, im->xorigin,im->yorigin+4,
1805                          im->xorigin,im->yorigin-im->ysize-4,
1806                          MGRIDWIDTH, im->graph_col[GRC_GRID]);
1807    
1808     
1809     /* arrow for X axis direction */
1810     gfx_new_area ( im->canvas, 
1811                    im->xorigin+im->xsize+3,  im->yorigin-3,
1812                    im->xorigin+im->xsize+3,  im->yorigin+4,
1813                    im->xorigin+im->xsize+8,  im->yorigin+0.5, /* LINEOFFSET */
1814                    im->graph_col[GRC_ARROW]);
1815    
1816    
1817    
1818 }
1819
1820 void
1821 grid_paint(image_desc_t   *im)
1822 {   
1823     long i;
1824     int res=0;
1825     double X0,Y0; /* points for filled graph and more*/
1826     gfx_node_t *node;
1827
1828     /* draw 3d border */
1829     node = gfx_new_area (im->canvas, 0,im->yimg,
1830                                  2,im->yimg-2,
1831                                  2,2,im->graph_col[GRC_SHADEA]);
1832     gfx_add_point( node , im->ximg - 2, 2 );
1833     gfx_add_point( node , im->ximg, 0 );
1834     gfx_add_point( node , 0,0 );
1835 /*    gfx_add_point( node , 0,im->yimg ); */
1836    
1837     node =  gfx_new_area (im->canvas, 2,im->yimg-2,
1838                                   im->ximg-2,im->yimg-2,
1839                                   im->ximg - 2, 2,
1840                                  im->graph_col[GRC_SHADEB]);
1841     gfx_add_point( node ,   im->ximg,0);
1842     gfx_add_point( node ,   im->ximg,im->yimg);
1843     gfx_add_point( node ,   0,im->yimg);
1844 /*    gfx_add_point( node , 0,im->yimg ); */
1845    
1846    
1847     if (im->draw_x_grid == 1 )
1848       vertical_grid(im);
1849     
1850     if (im->draw_y_grid == 1){
1851         if(im->logarithmic){
1852                 res = horizontal_log_grid(im);
1853         } else {
1854                 res = draw_horizontal_grid(im);
1855         }
1856
1857         /* dont draw horizontal grid if there is no min and max val */
1858         if (! res ) {
1859           char *nodata = "No Data found";
1860            gfx_new_text(im->canvas,im->ximg/2, (2*im->yorigin-im->ysize) / 2,
1861                         im->graph_col[GRC_FONT],
1862                         im->text_prop[TEXT_PROP_AXIS].font,
1863                         im->text_prop[TEXT_PROP_AXIS].size,
1864                         im->tabwidth, 0.0, GFX_H_CENTER, GFX_V_CENTER,
1865                         nodata );          
1866         }
1867     }
1868
1869     /* yaxis description */
1870         if (im->canvas->imgformat != IF_PNG) {
1871             gfx_new_text( im->canvas,
1872                           7, (im->yorigin - im->ysize/2),
1873                           im->graph_col[GRC_FONT],
1874                           im->text_prop[TEXT_PROP_AXIS].font,
1875                           im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1876                           GFX_H_CENTER, GFX_V_CENTER,
1877                           im->ylegend);
1878         } else {
1879             /* horrible hack until we can actually print vertically */
1880             {
1881                 int n;
1882                 int l=strlen(im->ylegend);
1883                 char s[2];
1884                 for (n=0;n<strlen(im->ylegend);n++) {
1885                     s[0]=im->ylegend[n];
1886                     s[1]='\0';
1887                     gfx_new_text(im->canvas,7,im->text_prop[TEXT_PROP_AXIS].size*(l-n),
1888                         im->graph_col[GRC_FONT],
1889                         im->text_prop[TEXT_PROP_AXIS].font,
1890                         im->text_prop[TEXT_PROP_AXIS].size, im->tabwidth, 270.0,
1891                         GFX_H_CENTER, GFX_V_CENTER,
1892                         s);
1893                 }
1894             }
1895         }
1896    
1897     /* graph title */
1898     gfx_new_text( im->canvas,
1899                   im->ximg/2, im->text_prop[TEXT_PROP_TITLE].size,
1900                   im->graph_col[GRC_FONT],
1901                   im->text_prop[TEXT_PROP_TITLE].font,
1902                   im->text_prop[TEXT_PROP_TITLE].size, im->tabwidth, 0.0,
1903                   GFX_H_CENTER, GFX_V_CENTER,
1904                   im->title);
1905
1906     /* graph labels */
1907     if( !(im->extra_flags & NOLEGEND) ) {
1908       for(i=0;i<im->gdes_c;i++){
1909         if(im->gdes[i].legend[0] =='\0')
1910             continue;
1911          
1912         /* im->gdes[i].leg_y is the bottom of the legend */
1913                 X0 = im->gdes[i].leg_x;
1914                 Y0 = im->gdes[i].leg_y;
1915                 /* Box needed? */
1916                 if (       im->gdes[i].gf != GF_GPRINT
1917                         && im->gdes[i].gf != GF_COMMENT) {
1918                     int boxH, boxV;
1919
1920                     boxH = gfx_get_text_width(im->canvas, 0,
1921                                 im->text_prop[TEXT_PROP_AXIS].font,
1922                                 im->text_prop[TEXT_PROP_AXIS].size,
1923                                 im->tabwidth,"M") * 1.25;
1924                     boxV = boxH;
1925
1926                     node = gfx_new_area(im->canvas,
1927                                 X0,Y0-boxV,
1928                                 X0,Y0,
1929                                 X0+boxH,Y0,
1930                                 im->gdes[i].col);
1931                     gfx_add_point ( node, X0+boxH, Y0-boxV );
1932                     node = gfx_new_line(im->canvas,
1933                                 X0,Y0-boxV, X0,Y0,
1934                                 1,0x000000FF);
1935                     gfx_add_point(node,X0+boxH,Y0);
1936                     gfx_add_point(node,X0+boxH,Y0-boxV);
1937                     gfx_close_path(node);
1938                     X0 += boxH / 1.25 * 2;
1939                 }
1940                 gfx_new_text ( im->canvas, X0, Y0,
1941                                    im->graph_col[GRC_FONT],
1942                                    im->text_prop[TEXT_PROP_AXIS].font,
1943                                    im->text_prop[TEXT_PROP_AXIS].size,
1944                                    im->tabwidth,0.0, GFX_H_LEFT, GFX_V_BOTTOM,
1945                                    im->gdes[i].legend );
1946               }
1947            }
1948         }
1949
1950
1951 /*****************************************************
1952  * lazy check make sure we rely need to create this graph
1953  *****************************************************/
1954
1955 int lazy_check(image_desc_t *im){
1956     FILE *fd = NULL;
1957         int size = 1;
1958     struct stat  imgstat;
1959     
1960     if (im->lazy == 0) return 0; /* no lazy option */
1961     if (stat(im->graphfile,&imgstat) != 0) 
1962       return 0; /* can't stat */
1963     /* one pixel in the existing graph is more then what we would
1964        change here ... */
1965     if (time(NULL) - imgstat.st_mtime > 
1966         (im->end - im->start) / im->xsize) 
1967       return 0;
1968     if ((fd = fopen(im->graphfile,"rb")) == NULL) 
1969       return 0; /* the file does not exist */
1970     switch (im->canvas->imgformat) {
1971     case IF_PNG:
1972            size = PngSize(fd,&(im->ximg),&(im->yimg));
1973            break;
1974     default:
1975            size = 1;
1976     }
1977     fclose(fd);
1978     return size;
1979 }
1980
1981 void
1982 pie_part(image_desc_t *im, gfx_color_t color,
1983             double PieCenterX, double PieCenterY, double Radius,
1984             double startangle, double endangle)
1985 {
1986     gfx_node_t *node;
1987     double angle;
1988     double step=M_PI/50; /* Number of iterations for the circle;
1989                          ** 10 is definitely too low, more than
1990                          ** 50 seems to be overkill
1991                          */
1992
1993     /* Strange but true: we have to work clockwise or else
1994     ** anti aliasing nor transparency don't work.
1995     **
1996     ** This test is here to make sure we do it right, also
1997     ** this makes the for...next loop more easy to implement.
1998     ** The return will occur if the user enters a negative number
1999     ** (which shouldn't be done according to the specs) or if the
2000     ** programmers do something wrong (which, as we all know, never
2001     ** happens anyway :)
2002     */
2003     if (endangle<startangle) return;
2004
2005     /* Hidden feature: Radius decreases each full circle */
2006     angle=startangle;
2007     while (angle>=2*M_PI) {
2008         angle  -= 2*M_PI;
2009         Radius *= 0.8;
2010     }
2011
2012     node=gfx_new_area(im->canvas,
2013                 PieCenterX+sin(startangle)*Radius,
2014                 PieCenterY-cos(startangle)*Radius,
2015                 PieCenterX,
2016                 PieCenterY,
2017                 PieCenterX+sin(endangle)*Radius,
2018                 PieCenterY-cos(endangle)*Radius,
2019                 color);
2020     for (angle=endangle;angle-startangle>=step;angle-=step) {
2021         gfx_add_point(node,
2022                 PieCenterX+sin(angle)*Radius,
2023                 PieCenterY-cos(angle)*Radius );
2024     }
2025 }
2026
2027 int
2028 graph_size_location(image_desc_t *im, int elements, int piechart )
2029 {
2030     /* The actual size of the image to draw is determined from
2031     ** several sources.  The size given on the command line is
2032     ** the graph area but we need more as we have to draw labels
2033     ** and other things outside the graph area
2034     */
2035
2036     /* +-+-------------------------------------------+
2037     ** |l|.................title.....................|
2038     ** |e+--+-------------------------------+--------+
2039     ** |b| b|                               |        |
2040     ** |a| a|                               |  pie   |
2041     ** |l| l|          main graph area      | chart  |
2042     ** |.| .|                               |  area  |
2043     ** |t| y|                               |        |
2044     ** |r+--+-------------------------------+--------+
2045     ** |e|  | x-axis labels                 |        |
2046     ** |v+--+-------------------------------+--------+
2047     ** | |..............legends......................|
2048     ** +-+-------------------------------------------+
2049     */
2050     int Xvertical=0,    Yvertical=0,
2051         Xtitle   =0,    Ytitle   =0,
2052         Xylabel  =0,    Yylabel  =0,
2053         Xmain    =0,    Ymain    =0,
2054         Xpie     =0,    Ypie     =0,
2055         Xxlabel  =0,    Yxlabel  =0,
2056 #if 0
2057         Xlegend  =0,    Ylegend  =0,
2058 #endif
2059         Xspacing =10,   Yspacing =10;
2060
2061     if (im->ylegend[0] != '\0') {
2062         Xvertical = im->text_prop[TEXT_PROP_LEGEND].size *2;
2063         Yvertical = im->text_prop[TEXT_PROP_LEGEND].size * (strlen(im->ylegend)+1);
2064     }
2065
2066     if (im->title[0] != '\0') {
2067         /* The title is placed "inbetween" two text lines so it
2068         ** automatically has some vertical spacing.  The horizontal
2069         ** spacing is added here, on each side.
2070         */
2071         Xtitle = gfx_get_text_width(im->canvas, 0,
2072                 im->text_prop[TEXT_PROP_TITLE].font,
2073                 im->text_prop[TEXT_PROP_TITLE].size,
2074                 im->tabwidth,
2075                 im->title) + 2*Xspacing;
2076         Ytitle = im->text_prop[TEXT_PROP_TITLE].size*2;
2077     }
2078
2079     if (elements) {
2080         Xmain=im->xsize;
2081         Ymain=im->ysize;
2082         if (im->draw_x_grid) {
2083             Xxlabel=Xmain;
2084             Yxlabel=im->text_prop[TEXT_PROP_LEGEND].size *2;
2085         }
2086         if (im->draw_y_grid) {
2087             Xylabel=im->text_prop[TEXT_PROP_LEGEND].size *6;
2088             Yylabel=Ymain;
2089         }
2090     }
2091
2092     if (piechart) {
2093         im->piesize=im->xsize<im->ysize?im->xsize:im->ysize;
2094         Xpie=im->piesize;
2095         Ypie=im->piesize;
2096     }
2097
2098     /* Now calculate the total size.  Insert some spacing where
2099        desired.  im->xorigin and im->yorigin need to correspond
2100        with the lower left corner of the main graph area or, if
2101        this one is not set, the imaginary box surrounding the
2102        pie chart area. */
2103
2104     /* The legend width cannot yet be determined, as a result we
2105     ** have problems adjusting the image to it.  For now, we just
2106     ** forget about it at all; the legend will have to fit in the
2107     ** size already allocated.
2108     */
2109     im->ximg = Xylabel + Xmain + Xpie + Xspacing;
2110     if (Xmain) im->ximg += Xspacing;
2111     if (Xpie) im->ximg += Xspacing;
2112     im->xorigin = Xspacing + Xylabel;
2113     if (Xtitle > im->ximg) im->ximg = Xtitle;
2114     if (Xvertical) {
2115         im->ximg += Xvertical;
2116         im->xorigin += Xvertical;
2117     }
2118     xtr(im,0);
2119
2120     /* The vertical size is interesting... we need to compare
2121     ** the sum of {Ytitle, Ymain, Yxlabel, Ylegend} with Yvertical
2122     ** however we need to know {Ytitle+Ymain+Yxlabel} in order to
2123     ** start even thinking about Ylegend.
2124     **
2125     ** Do it in three portions: First calculate the inner part,
2126     ** then do the legend, then adjust the total height of the img.
2127     */
2128
2129     /* reserve space for main and/or pie */
2130     im->yimg = Ymain + Yxlabel;
2131     if (im->yimg < Ypie) im->yimg = Ypie;
2132     im->yorigin = im->yimg - Yxlabel;
2133     /* reserve space for the title *or* some padding above the graph */
2134     if (Ytitle) {
2135         im->yimg += Ytitle;
2136         im->yorigin += Ytitle;
2137     } else {
2138         im->yimg += Yspacing;
2139         im->yorigin += Yspacing;
2140     }
2141     /* reserve space for padding below the graph */
2142     im->yimg += Yspacing;
2143     ytr(im,DNAN);
2144
2145     /* Determine where to place the legends onto the image.
2146     ** Adjust im->yimg to match the space requirements.
2147     */
2148     if(leg_place(im)==-1)
2149         return -1;
2150
2151     /* last of three steps: check total height of image */
2152     if (im->yimg < Yvertical) im->yimg = Yvertical;
2153
2154 #if 0
2155     if (Xlegend > im->ximg) {
2156         im->ximg = Xlegend;
2157         /* reposition Pie */
2158     }
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].stack=0;
2493     im->gdes[im->gdes_c-1].start=im->start; 
2494     im->gdes[im->gdes_c-1].end=im->end; 
2495     im->gdes[im->gdes_c-1].vname[0]='\0'; 
2496     im->gdes[im->gdes_c-1].data=NULL;
2497     im->gdes[im->gdes_c-1].ds_namv=NULL;
2498     im->gdes[im->gdes_c-1].data_first=0;
2499     im->gdes[im->gdes_c-1].p_data=NULL;
2500     im->gdes[im->gdes_c-1].rpnp=NULL;
2501     im->gdes[im->gdes_c-1].col = 0x0;
2502     im->gdes[im->gdes_c-1].legend[0]='\0';
2503     im->gdes[im->gdes_c-1].rrd[0]='\0';
2504     im->gdes[im->gdes_c-1].ds=-1;    
2505     im->gdes[im->gdes_c-1].p_data=NULL;    
2506     return 0;
2507 }
2508
2509 /* copies input untill the first unescaped colon is found
2510    or until input ends. backslashes have to be escaped as well */
2511 int
2512 scan_for_col(char *input, int len, char *output)
2513 {
2514     int inp,outp=0;
2515     for (inp=0; 
2516          inp < len &&
2517            input[inp] != ':' &&
2518            input[inp] != '\0';
2519          inp++){
2520       if (input[inp] == '\\' &&
2521           input[inp+1] != '\0' && 
2522           (input[inp+1] == '\\' ||
2523            input[inp+1] == ':')){
2524         output[outp++] = input[++inp];
2525       }
2526       else {
2527         output[outp++] = input[inp];
2528       }
2529     }
2530     output[outp] = '\0';
2531     return inp;
2532 }
2533 /* Some surgery done on this function, it became ridiculously big.
2534 ** Things moved:
2535 ** - initializing     now in rrd_graph_init()
2536 ** - options parsing  now in rrd_graph_options()
2537 ** - script parsing   now in rrd_graph_script()
2538 */
2539 int 
2540 rrd_graph(int argc, char **argv, char ***prdata, int *xsize, int *ysize)
2541 {
2542     image_desc_t   im;
2543
2544 #ifdef HAVE_TZSET
2545     tzset();
2546 #endif
2547 #ifdef HAVE_SETLOCALE
2548     setlocale(LC_TIME,"");
2549 #endif
2550             
2551             
2552     rrd_graph_init(&im);
2553
2554     rrd_graph_options(argc,argv,&im);
2555     if (rrd_test_error()) return -1;
2556     
2557     if (strlen(argv[optind])>=MAXPATH) {
2558         rrd_set_error("filename (including path) too long");
2559         return -1;
2560     }
2561     strncpy(im.graphfile,argv[optind],MAXPATH-1);
2562     im.graphfile[MAXPATH-1]='\0';
2563
2564     rrd_graph_script(argc,argv,&im);
2565     if (rrd_test_error()) return -1;
2566
2567     /* Everything is now read and the actual work can start */
2568
2569     (*prdata)=NULL;
2570     if (graph_paint(&im,prdata)==-1){
2571         im_free(&im);
2572         return -1;
2573     }
2574
2575     /* The image is generated and needs to be output.
2576     ** Also, if needed, print a line with information about the image.
2577     */
2578
2579     *xsize=im.ximg;
2580     *ysize=im.yimg;
2581     if (im.imginfo) {
2582         char *filename;
2583         if (!(*prdata)) {
2584             /* maybe prdata is not allocated yet ... lets do it now */
2585             if ((*prdata = calloc(2,sizeof(char *)))==NULL) {
2586                 rrd_set_error("malloc imginfo");
2587                 return -1; 
2588             };
2589         }
2590         if(((*prdata)[0] = malloc((strlen(im.imginfo)+200+strlen(im.graphfile))*sizeof(char)))
2591          ==NULL){
2592             rrd_set_error("malloc imginfo");
2593             return -1;
2594         }
2595         filename=im.graphfile+strlen(im.graphfile);
2596         while(filename > im.graphfile) {
2597             if (*(filename-1)=='/' || *(filename-1)=='\\' ) break;
2598             filename--;
2599         }
2600
2601         sprintf((*prdata)[0],im.imginfo,filename,(long)(im.canvas->zoom*im.ximg),(long)(im.canvas->zoom*im.yimg));
2602     }
2603     im_free(&im);
2604     return 0;
2605 }
2606
2607 void
2608 rrd_graph_init(image_desc_t *im)
2609 {
2610     int i;
2611
2612     im->xlab_user.minsec = -1;
2613     im->ximg=0;
2614     im->yimg=0;
2615     im->xsize = 400;
2616     im->ysize = 100;
2617     im->step = 0;
2618     im->ylegend[0] = '\0';
2619     im->title[0] = '\0';
2620     im->minval = DNAN;
2621     im->maxval = DNAN;    
2622     im->unitsexponent= 9999;
2623     im->extra_flags= 0;
2624     im->rigid = 0;
2625     im->gridfit = 1;
2626     im->imginfo = NULL;
2627     im->lazy = 0;
2628     im->logarithmic = 0;
2629     im->ygridstep = DNAN;
2630     im->draw_x_grid = 1;
2631     im->draw_y_grid = 1;
2632     im->base = 1000;
2633     im->prt_c = 0;
2634     im->gdes_c = 0;
2635     im->gdes = NULL;
2636     im->canvas = gfx_new_canvas();
2637     im->grid_dash_on = 1;
2638     im->grid_dash_off = 1;
2639
2640     for(i=0;i<DIM(graph_col);i++)
2641         im->graph_col[i]=graph_col[i];
2642
2643     for(i=0;i<DIM(text_prop);i++){        
2644       im->text_prop[i].size = text_prop[i].size;
2645       im->text_prop[i].font = text_prop[i].font;
2646     }
2647 }
2648
2649 void
2650 rrd_graph_options(int argc, char *argv[],image_desc_t *im)
2651 {
2652     int                 stroff;    
2653     char                *parsetime_error = NULL;
2654     char                scan_gtm[12],scan_mtm[12],scan_ltm[12],col_nam[12];
2655     time_t              start_tmp=0,end_tmp=0;
2656     long                long_tmp;
2657     struct time_value   start_tv, end_tv;
2658     gfx_color_t         color;
2659
2660     parsetime("end-24h", &start_tv);
2661     parsetime("now", &end_tv);
2662
2663     while (1){
2664         static struct option long_options[] =
2665         {
2666             {"start",      required_argument, 0,  's'},
2667             {"end",        required_argument, 0,  'e'},
2668             {"x-grid",     required_argument, 0,  'x'},
2669             {"y-grid",     required_argument, 0,  'y'},
2670             {"vertical-label",required_argument,0,'v'},
2671             {"width",      required_argument, 0,  'w'},
2672             {"height",     required_argument, 0,  'h'},
2673             {"interlaced", no_argument,       0,  'i'},
2674             {"upper-limit",required_argument, 0,  'u'},
2675             {"lower-limit",required_argument, 0,  'l'},
2676             {"rigid",      no_argument,       0,  'r'},
2677             {"base",       required_argument, 0,  'b'},
2678             {"logarithmic",no_argument,       0,  'o'},
2679             {"color",      required_argument, 0,  'c'},
2680             {"font",       required_argument, 0,  'n'},
2681             {"title",      required_argument, 0,  't'},
2682             {"imginfo",    required_argument, 0,  'f'},
2683             {"imgformat",  required_argument, 0,  'a'},
2684             {"lazy",       no_argument,       0,  'z'},
2685             {"zoom",       required_argument, 0,  'm'},
2686             {"no-legend",  no_argument,       0,  'g'},
2687             {"alt-y-grid", no_argument,       0,   257 },
2688             {"alt-autoscale", no_argument,    0,   258 },
2689             {"alt-autoscale-max", no_argument,    0,   259 },
2690             {"units-exponent",required_argument, 0,  260},
2691             {"step",       required_argument, 0,   261},
2692             {"no-gridfit", no_argument,       0,   262},
2693             {0,0,0,0}};
2694         int option_index = 0;
2695         int opt;
2696
2697
2698         opt = getopt_long(argc, argv, 
2699                           "s:e:x:y:v:w:h:iu:l:rb:oc:n:m:t:f:a:z:g",
2700                           long_options, &option_index);
2701
2702         if (opt == EOF)
2703             break;
2704         
2705         switch(opt) {
2706         case 257:
2707             im->extra_flags |= ALTYGRID;
2708             break;
2709         case 258:
2710             im->extra_flags |= ALTAUTOSCALE;
2711             break;
2712         case 259:
2713             im->extra_flags |= ALTAUTOSCALE_MAX;
2714             break;
2715         case 'g':
2716             im->extra_flags |= NOLEGEND;
2717             break;
2718         case 260:
2719             im->unitsexponent = atoi(optarg);
2720             break;
2721         case 261:
2722             im->step =  atoi(optarg);
2723             break;
2724         case 262:
2725             im->gridfit = 0;
2726             break;
2727         case 's':
2728             if ((parsetime_error = parsetime(optarg, &start_tv))) {
2729                 rrd_set_error( "start time: %s", parsetime_error );
2730                 return;
2731             }
2732             break;
2733         case 'e':
2734             if ((parsetime_error = parsetime(optarg, &end_tv))) {
2735                 rrd_set_error( "end time: %s", parsetime_error );
2736                 return;
2737             }
2738             break;
2739         case 'x':
2740             if(strcmp(optarg,"none") == 0){
2741               im->draw_x_grid=0;
2742               break;
2743             };
2744                 
2745             if(sscanf(optarg,
2746                       "%10[A-Z]:%ld:%10[A-Z]:%ld:%10[A-Z]:%ld:%ld:%n",
2747                       scan_gtm,
2748                       &im->xlab_user.gridst,
2749                       scan_mtm,
2750                       &im->xlab_user.mgridst,
2751                       scan_ltm,
2752                       &im->xlab_user.labst,
2753                       &im->xlab_user.precis,
2754                       &stroff) == 7 && stroff != 0){
2755                 strncpy(im->xlab_form, optarg+stroff, sizeof(im->xlab_form) - 1);
2756                 if((im->xlab_user.gridtm = tmt_conv(scan_gtm)) == -1){
2757                     rrd_set_error("unknown keyword %s",scan_gtm);
2758                     return;
2759                 } else if ((im->xlab_user.mgridtm = tmt_conv(scan_mtm)) == -1){
2760                     rrd_set_error("unknown keyword %s",scan_mtm);
2761                     return;
2762                 } else if ((im->xlab_user.labtm = tmt_conv(scan_ltm)) == -1){
2763                     rrd_set_error("unknown keyword %s",scan_ltm);
2764                     return;
2765                 } 
2766                 im->xlab_user.minsec = 1;
2767                 im->xlab_user.stst = im->xlab_form;
2768             } else {
2769                 rrd_set_error("invalid x-grid format");
2770                 return;
2771             }
2772             break;
2773         case 'y':
2774
2775             if(strcmp(optarg,"none") == 0){
2776               im->draw_y_grid=0;
2777               break;
2778             };
2779
2780             if(sscanf(optarg,
2781                       "%lf:%d",
2782                       &im->ygridstep,
2783                       &im->ylabfact) == 2) {
2784                 if(im->ygridstep<=0){
2785                     rrd_set_error("grid step must be > 0");
2786                     return;
2787                 } else if (im->ylabfact < 1){
2788                     rrd_set_error("label factor must be > 0");
2789                     return;
2790                 } 
2791             } else {
2792                 rrd_set_error("invalid y-grid format");
2793                 return;
2794             }
2795             break;
2796         case 'v':
2797             strncpy(im->ylegend,optarg,150);
2798             im->ylegend[150]='\0';
2799             break;
2800         case 'u':
2801             im->maxval = atof(optarg);
2802             break;
2803         case 'l':
2804             im->minval = atof(optarg);
2805             break;
2806         case 'b':
2807             im->base = atol(optarg);
2808             if(im->base != 1024 && im->base != 1000 ){
2809                 rrd_set_error("the only sensible value for base apart from 1000 is 1024");
2810                 return;
2811             }
2812             break;
2813         case 'w':
2814             long_tmp = atol(optarg);
2815             if (long_tmp < 10) {
2816                 rrd_set_error("width below 10 pixels");
2817                 return;
2818             }
2819             im->xsize = long_tmp;
2820             break;
2821         case 'h':
2822             long_tmp = atol(optarg);
2823             if (long_tmp < 10) {
2824                 rrd_set_error("height below 10 pixels");
2825                 return;
2826             }
2827             im->ysize = long_tmp;
2828             break;
2829         case 'i':
2830             im->canvas->interlaced = 1;
2831             break;
2832         case 'r':
2833             im->rigid = 1;
2834             break;
2835         case 'f':
2836             im->imginfo = optarg;
2837             break;
2838         case 'a':
2839             if((im->canvas->imgformat = if_conv(optarg)) == -1) {
2840                 rrd_set_error("unsupported graphics format '%s'",optarg);
2841                 return;
2842             }
2843             break;
2844         case 'z':
2845             im->lazy = 1;
2846             break;
2847         case 'o':
2848             im->logarithmic = 1;
2849             if (isnan(im->minval))
2850                 im->minval=1;
2851             break;
2852         case 'c':
2853             if(sscanf(optarg,
2854                       "%10[A-Z]#%8lx",
2855                       col_nam,&color) == 2){
2856                 int ci;
2857                 if((ci=grc_conv(col_nam)) != -1){
2858                     im->graph_col[ci]=color;
2859                 }  else {
2860                   rrd_set_error("invalid color name '%s'",col_nam);
2861                 }
2862             } else {
2863                 rrd_set_error("invalid color def format");
2864                 return;
2865             }
2866             break;        
2867         case 'n':{
2868                         /* originally this used char *prop = "" and
2869                         ** char *font = "dummy" however this results
2870                         ** in a SEG fault, at least on RH7.1
2871                         **
2872                         ** The current implementation isn't proper
2873                         ** either, font is never freed and prop uses
2874                         ** a fixed width string
2875                         */
2876             char prop[100];
2877             double size = 1;
2878             char *font;
2879
2880             font=malloc(255);
2881             if(sscanf(optarg,
2882                                 "%10[A-Z]:%lf:%s",
2883                                 prop,&size,font) == 3){
2884                 int sindex;
2885                 if((sindex=text_prop_conv(prop)) != -1){
2886                     im->text_prop[sindex].size=size;              
2887                     im->text_prop[sindex].font=font;
2888                     if (sindex==0) { /* the default */
2889                         im->text_prop[TEXT_PROP_TITLE].size=size;
2890                         im->text_prop[TEXT_PROP_TITLE].font=font;
2891                         im->text_prop[TEXT_PROP_AXIS].size=size;
2892                         im->text_prop[TEXT_PROP_AXIS].font=font;
2893                         im->text_prop[TEXT_PROP_UNIT].size=size;
2894                         im->text_prop[TEXT_PROP_UNIT].font=font;
2895                         im->text_prop[TEXT_PROP_LEGEND].size=size;
2896                         im->text_prop[TEXT_PROP_LEGEND].font=font;
2897                     }
2898                 } else {
2899                     rrd_set_error("invalid fonttag '%s'",prop);
2900                     return;
2901                 }
2902             } else {
2903                 rrd_set_error("invalid text property format");
2904                 return;
2905             }
2906             break;          
2907         }
2908         case 'm':
2909             im->canvas->zoom = atof(optarg);
2910             if (im->canvas->zoom <= 0.0) {
2911                 rrd_set_error("zoom factor must be > 0");
2912                 return;
2913             }
2914           break;
2915         case 't':
2916             strncpy(im->title,optarg,150);
2917             im->title[150]='\0';
2918             break;
2919
2920         case '?':
2921             if (optopt != 0)
2922                 rrd_set_error("unknown option '%c'", optopt);
2923             else
2924                 rrd_set_error("unknown option '%s'",argv[optind-1]);
2925             return;
2926         }
2927     }
2928     
2929     if (optind >= argc) {
2930        rrd_set_error("missing filename");
2931        return;
2932     }
2933
2934     if (im->logarithmic == 1 && (im->minval <= 0 || isnan(im->minval))){
2935         rrd_set_error("for a logarithmic yaxis you must specify a lower-limit > 0");    
2936         return;
2937     }
2938
2939     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
2940         /* error string is set in parsetime.c */
2941         return;
2942     }  
2943     
2944     if (start_tmp < 3600*24*365*10){
2945         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
2946         return;
2947     }
2948     
2949     if (end_tmp < start_tmp) {
2950         rrd_set_error("start (%ld) should be less than end (%ld)", 
2951                start_tmp, end_tmp);
2952         return;
2953     }
2954     
2955     im->start = start_tmp;
2956     im->end = end_tmp;
2957 }
2958
2959 /* rrd_name_or_num()
2960 **
2961 ** Scans for a VDEF-variable or a number
2962 **
2963 ** Returns an integer describing what was found:
2964 **
2965 ** 0: error
2966 ** 1: found an integer; it is returned in both l and d
2967 ** 2: found a float; it is returned in d
2968 ** 3: found a vname; its index is returned in l
2969 **
2970 ** l and d are undefined unless described above
2971 */
2972 static int
2973 rrd_name_or_num(image_desc_t *im, char *param, long *l, double *d)
2974 {
2975     int i1=0,i2=0,i3=0,i4=0,i5=0,i6=0;
2976     char vname[MAX_VNAME_LEN+1];
2977
2978     sscanf(param, "%li%n%*s%n", l,&i1,&i2);
2979     sscanf(param, "%lf%n%*s%n", d,&i3,&i4);
2980     sscanf(param, DEF_NAM_FMT "%n%*s%n", vname, &i5,&i6);
2981
2982     if ( (i1) && (!i2) ) return 1;
2983     if ( (i3) && (!i4) ) return 2;
2984     if ( (i5) && (!i6) ) {
2985         if ((*l = find_var(im,vname))!=-1) return 3;
2986     }
2987     return 0;
2988 }
2989
2990 /* rrd_vname_color()
2991 **
2992 ** Parses "[<vname|number>][#color]" where at least one
2993 ** of the optional strings must exist.
2994 **
2995 ** Returns an integer describing what was found.
2996 ** If the result is 0, the rrd_error string may be set.
2997 **
2998 ** ...CVVVV 
2999 ** ---------:-----------------------------------
3000 ** 00000000 : error
3001 ** ....0000 : a value/variable was not found
3002 ** ....0001 : an integer number was found, returned in both l and d
3003 ** ....0010 : a floating point number was found, returned in d
3004 ** ....0011 : reserved for future values
3005 ** ....01xx : reserved for future values
3006 ** ....1000 : an existing  DEF vname was found, idx returned in l
3007 ** ....1001 : an existing CDEF vname was found, idx returned in l
3008 ** ....1010 : an existing VDEF vname was found, idx returned in l
3009 ** ....1011 : reserved for future variables
3010 ** ....11xx : reserved for future variables
3011 ** ...0.... : a color was not found, returned in color
3012 ** ...1.... : a color was found, returned in color
3013 */
3014 static int
3015 rrd_vname_color(image_desc_t *im, char * param,
3016                         long *l,
3017                         double *d,
3018                         gfx_color_t *color)
3019 {
3020     int result=0,i=0;
3021
3022     if (param[0]!='#') { /* vname or num present, or empty string */
3023         char *s,*c=param;
3024         while ((*c!='\0')&&(*c!='#')) c++,i++;
3025         if (*c!='\0') {
3026             s=malloc(i+1);
3027             if (s==NULL) {
3028                 rrd_set_error("Out of memory in function rrd_vname_color");
3029                 return 0;
3030             }
3031             strncpy(s,param,i);
3032             s[i]='\0';
3033             result=rrd_name_or_num(im, s, l, d);
3034             if (!result) {
3035                 rrd_set_error("Use of uninitialized vname %s",s);
3036                 free(s);
3037             }
3038         } else {
3039             result=rrd_name_or_num(im, param, l, d);
3040             if (!result) {
3041                 rrd_set_error("Use of uninitialized vname %s",param);
3042             }
3043         }
3044         switch (result) {
3045             case 0: return 0; /* error set above */
3046             case 1:
3047             case 2: break;
3048             case 3:
3049                 switch (im->gdes[*l].gf) {
3050                     case GF_DEF:        result=0x08;break;
3051                     case GF_CDEF:       result=0x09;break;
3052                     case GF_VDEF:       result=0x0A;break;
3053                     default:
3054                         rrd_set_error("Unexpected GF result from function "
3055                         "rrd_name_or_num() called from rrd_vname_color");
3056                         return 0;
3057                 }
3058                 break;
3059             default:
3060                 rrd_set_error("Unexpected result from function "
3061                         "rrd_name_or_num() called from rrd_vname_color");
3062                 return 0;
3063         }
3064     }
3065     /* Parse color, if any. */
3066     if (param[i] == '\0') return result;
3067     else {
3068         unsigned int r=0,g=0,b=0,a=0xFF;
3069         int i1=0,i2=0;
3070         sscanf(&param[i], "#%02x%02x%02x%n%02x%n",
3071                                 &r,&g,&b,&i1,&a,&i2);
3072         if (!i1) {
3073             rrd_set_error("Unparsable color %s",&param[i]);
3074             return 0;
3075         }
3076         if (i2) i1=i2;
3077         i2=0;
3078         sscanf(&param[i+i1],"%*s%n",&i2);
3079         if (i2) {
3080             rrd_set_error("Garbage after color %s",param[i]);
3081             return 0;
3082         }
3083         *color=r<<24|g<<16|b<<8|a;
3084         return result|0x10;
3085     }
3086 }
3087
3088 /* rrd_find_function()
3089 **
3090 ** Checks if the parameter is a valid function and
3091 ** if so, returns it in the graph description pointer.
3092 **
3093 ** The return value is a boolean; true if found
3094 */
3095 static int
3096 rrd_find_function(char *param, graph_desc_t *gdp)
3097 {
3098     size_t i1=0,i2=0;
3099     char funcname[11];
3100
3101     sscanf(param,"%10[A-Z]%n%*1[1-3]%n",funcname,(int *)&i1,(int *)&i2);
3102     gdp->gf=gf_conv(funcname);
3103     if ((int)gdp->gf == -1) {
3104         rrd_set_error("'%s' is not a valid function name",funcname);
3105         return 0;
3106     }
3107     if (gdp->gf==GF_LINE) {
3108         if (i2) {
3109             gdp->linewidth=param[i1]-'0';
3110         } else {
3111             rrd_set_error("LINE should have a width");
3112             return 0;
3113         }
3114     } else {
3115         if (i2) {
3116             rrd_set_error("Only LINE should have a width: %s",param);
3117             return 0;
3118         } else {
3119             i2=i1;
3120         }
3121     }
3122     if (strlen(param) != i2) {
3123         rrd_set_error("Garbage after function name: %s",param);
3124         return 0;
3125     }
3126     return 1;
3127 }
3128 /* rrd_split_line()
3129 **
3130 ** Takes a string as input; splits this line into multiple
3131 ** parameters on each ":" boundary.
3132 **
3133 ** If this function returns successful, the caller will have
3134 ** to free() the allocated memory for param.
3135 **
3136 ** The input string is destroyed, its memory is used by the
3137 ** output array.
3138 */
3139 static int
3140 rrd_split_line(char *line,char ***param)
3141 {
3142     int i=0,n=0,quoted=0;
3143     char *src=line;
3144     char *dst=line;
3145
3146     /* scan the amount of colons in the line.  We need
3147     ** at most this amount+1 pointers for the array. If
3148     ** any colons are escaped we waste some space.
3149     */
3150     if (*src!='\0') n=1;
3151
3152     while (*src != '\0')
3153         if (*src++ == ':') n++;
3154
3155     if (n==0) {
3156         rrd_set_error("No line to split. rrd_split_line was given the empty string.");
3157         return -1;
3158     }
3159
3160     src = line;
3161
3162     /* Allocate memory for an array of n char pointers */
3163     *param=calloc(n,sizeof(char *));
3164     if (*param==NULL) {
3165         rrd_set_error("Memory allocation failed inside rrd_split_line");
3166         return -1;
3167     }
3168
3169     /* split the line and fill the array */
3170    
3171     i=0;
3172     (*param)[i] = dst;
3173     while (*src != '\0') {
3174         switch (*src) {
3175             case '\'':
3176                 quoted^=1;
3177                 src++;
3178                 break;
3179             case '\\':  /* could be \:  but also \n */
3180                 src++;
3181                 switch (*src) {
3182                     case '\0':
3183                         free(*param);
3184                         rrd_set_error("Lone backslash inside rrd_split_line");
3185                         return -1;
3186                     case ':':
3187                         *dst++=*src++;
3188                         break;
3189                     default:
3190                         *dst++='\\';
3191                         *dst++=*src++;
3192                 }
3193                 break;
3194             case ':':
3195                 if (quoted) *dst++=*src++;
3196                 else {
3197                     *dst++ = '\0';
3198                     src++;
3199                     i++;
3200                     (*param)[i] = dst;
3201                 }
3202                 break;
3203             default:
3204                 *dst++=*src++;
3205         }
3206     }
3207     *dst='\0';
3208     i++; /* i separators means i+1 parameters */
3209
3210     return i;
3211 }
3212
3213 void
3214 rrd_graph_script_parse_def(int argc,char *argv[],int used,graph_desc_t *gdp) {
3215     time_t              start_tmp=0,end_tmp=0;
3216     struct time_value   start_tv, end_tv;
3217     char                *parsetime_error = NULL;
3218     struct option long_options[] = {
3219         { "step",       required_argument, NULL, 256 },
3220         { "start",      required_argument, NULL, 's' },
3221         { "end",        required_argument, NULL, 'e' },
3222         { NULL,0,NULL,0 }};
3223     int option_index = 0;
3224     int opt;
3225
3226     opterr=0;
3227     optind=used;
3228
3229     start_tv.type=ABSOLUTE_TIME;
3230     start_tv.offset=0;
3231     end_tv.type=ABSOLUTE_TIME;
3232     end_tv.offset=0;
3233     memcpy(&start_tv.tm, gmtime(&gdp->start) , sizeof(struct tm) );
3234     memcpy(&end_tv.tm,   gmtime(&gdp->end) ,   sizeof(struct tm) );
3235
3236     while (1) {
3237         opt = getopt_long(argc,argv,"-s:e:", long_options,&option_index);
3238         if (opt==-1) break;
3239         switch (opt) {
3240             case   1:
3241                 printf("DEBUG: found non-option[%i] %s\n",optind,optarg);
3242                 break;
3243             case 's':
3244                 if ((parsetime_error = parsetime(optarg, &start_tv))) {
3245                     rrd_set_error( "DEF start time: %s", parsetime_error );
3246                     return;
3247                 }
3248                 break;
3249             case 'e':
3250                 if ((parsetime_error = parsetime(optarg, &end_tv))) {
3251                     rrd_set_error( "DEF end time: %s", parsetime_error );
3252                     return;
3253                 }
3254                 break;
3255             case 256:
3256                 gdp->step = atol(optarg);
3257                 break;
3258             case '?':
3259                 printf("DEBUG: unrecognized option %s\n",argv[optind]);
3260                 break;
3261             case ':':
3262                 printf("DEBUG: option %c needs parameter\n",optopt);
3263                 break;
3264             default:
3265                 printf("DEBUG: getopt returned unknown char %c\n",opt);
3266                 break;
3267         }
3268     }
3269     if (optind < argc) {
3270         printf("DEBUG: %i remaining parameters:\n",argc-optind);
3271         while (optind<argc)
3272             printf("DEBUG: %3i: %s\n",optind,argv[optind++]);
3273         printf("\n");
3274     }
3275
3276     if (proc_start_end(&start_tv,&end_tv,&start_tmp,&end_tmp) == -1){
3277         /* error string is set in parsetime.c */
3278         return;
3279     }  
3280     
3281     if (start_tmp < 3600*24*365*10){
3282         rrd_set_error("the first entry to fetch should be after 1980 (%ld)",start_tmp);
3283         return;
3284     }
3285     
3286     if (end_tmp < start_tmp) {
3287         rrd_set_error("start (%ld) should be less than end (%ld)", 
3288                start_tmp, end_tmp);
3289         return;
3290     }
3291     
3292     gdp->start = start_tmp;
3293     gdp->end = end_tmp;
3294 }
3295
3296 void
3297 rrd_graph_script(int argc, char *argv[], image_desc_t *im)
3298 {
3299     int         i;
3300     char        symname[100];
3301     int         linepass = 0; /* stack must follow LINE*, AREA or STACK */    
3302     char **     param;
3303     int         paramcnt,paramused;
3304
3305     for (i=optind+1;i<argc;i++) {
3306         int             argstart=0;
3307         int             strstart=0;
3308         graph_desc_t    *gdp;
3309         char            *line;
3310         char    tmpline[256];
3311         char            vname[MAX_VNAME_LEN+1],sep[1];
3312 /*      double          d;      */
3313         int             j,k,l/*,m*/;
3314
3315         /* Each command is one element from *argv[].  This command is
3316         ** split at every unescaped colon.
3317         **
3318         ** Each command defines the most current gdes inside struct im.
3319         ** In stead of typing "im->gdes[im->gdes_c-1]" we use "gdp".
3320         */
3321         gdes_alloc(im);
3322         gdp=&im->gdes[im->gdes_c-1];
3323         strcpy(tmpline,argv[i]);
3324         line=tmpline;
3325         if ((paramcnt=rrd_split_line(argv[i],&param))==-1) return;
3326         paramused=0;
3327
3328 #ifdef DEBUG
3329         printf("DEBUG: after splitting line:\n");
3330         for (j=0;j<paramcnt;j++)
3331             printf("DEBUG: %3i: %s\n",j,param[j]);
3332 #endif
3333
3334         if (!rrd_find_function(param[paramused],gdp)) {
3335             im_free(im);
3336             free(param);
3337             return;
3338         }
3339         paramused++;
3340
3341         /* function:newvname=string[:ds-name:CF]        for xDEF
3342         ** function:vname[#color[:string]]              for LINEx,AREA,STACK
3343         ** function:vname#color[:num[:string]]          for TICK
3344         ** function:vname-or-num#color[:string]         for xRULE,PART
3345         ** function:vname:CF:string                     for xPRINT
3346         ** function:string                              for COMMENT
3347         */
3348
3349 /*TEMP*/argstart=strlen(param[paramused-1])+1;
3350
3351         /* If anything fails just use rrd_set_error() and break from the
3352         ** switch.  Just after the switch we call rrd_test_error() and
3353         ** clean up if it is set.
3354         */
3355         switch (gdp->gf) {
3356             case GF_XPORT:
3357                 break;
3358             case GF_COMMENT:
3359                 if (paramcnt<2) {
3360                     rrd_set_error("Not enough parameters for %s",param[0]);
3361                     break;
3362                 }
3363                 if (strlen(param[1])>FMT_LEG_LEN) {
3364                     rrd_set_error("Comment too long: %s:%s",param[0],param[1]);
3365                     break;
3366                 }
3367                 strcpy(gdp->legend,param[1]);
3368                 paramused++;
3369                 break;
3370             case GF_PART:
3371             case GF_VRULE:
3372             case GF_HRULE:
3373                 if (paramcnt<2) {
3374                     rrd_set_error("No name or number in %s",param[0]);
3375                     break;
3376                 }
3377                 j=rrd_vname_color(im,param[1],
3378                                 &gdp->xrule,&gdp->yrule,&gdp->col);
3379                 paramused++;
3380                 if (!j) break; /* error string set by function */
3381                 switch (j&0x0F) {
3382                     case 0x00:
3383                         rrd_set_error("Cannot parse name nor number "
3384                                 "in %s:%s",param[0],param[1]);
3385                         break;
3386                     case 0x08:
3387                     case 0x09:
3388                         rrd_set_error("Cannot use DEF or CDEF based "
3389                                 "variable in %s:%s",param[0],param[1]);
3390                         break;
3391                     case 0x0A:
3392                         gdp->vidx=gdp->xrule;
3393                         gdp->xrule=0;
3394                         gdp->yrule=DNAN;
3395                         break;
3396                     case 0x01:
3397                     case 0x02:
3398                         break;
3399                     default:
3400                         rrd_set_error("Unexpected result while parsing "
3401                                 "%s:%s, program error",param[0],param[1]);
3402                 }
3403                 if (rrd_test_error()) break;
3404
3405                 if (paramcnt>paramused) {
3406                     if (strlen(param[paramused])>FMT_LEG_LEN) {
3407                         rrd_set_error("Comment too long: %s:%s",
3408                                                 param[0],param[1]);
3409                         break;
3410                     }
3411                     strcpy(gdp->legend,param[paramused]);
3412                     paramused++;
3413                 }
3414                 break;
3415             case GF_STACK:
3416                 if (linepass==0) {
3417                     rrd_set_error("STACK must follow another graphing element");
3418                     break;
3419                 }
3420             case GF_LINE:
3421             case GF_AREA:
3422             case GF_TICK:
3423                         /* LINEx:vname[#color[:legend]][:STACK]
3424                         ** AREA:vname[#color[:legend]][:STACK]
3425                         ** STACK:vname[#color[:legend]]
3426                         ** TICK:vname#color[:num[:legend]]
3427                         */
3428                 linepass=1;
3429                 j=rrd_vname_color(im,param[paramused++],
3430                                 &gdp->vidx,&gdp->yrule,&gdp->col);
3431                 if (!j) break;  /* error string set by function */
3432                 switch (j&0x0F) {
3433                     case 0x00:
3434                         rrd_set_error("Cannot parse name nor number "
3435                                         "in %s:%s",param[0],param[1]);
3436                         break;
3437                     case 0x01:
3438                     case 0x02:
3439                         rrd_set_error("Cannot %s a number",param[0]);
3440                         break;
3441                     case 0x08:
3442                     case 0x09:
3443                         break;
3444                     case 0x0A:
3445                         rrd_set_error("Cannot use VDEF based variable "
3446                                         "with %s %s",param[0],gdp->vname);
3447                         break;
3448                 }
3449                 if (rrd_test_error()) break;
3450
3451                 if (gdp->gf == GF_TICK) {
3452                     if (!(j&0x10)) {
3453                         rrd_set_error("Color not optional for TICK");
3454                         break;
3455                     } else { /* parse optional number */
3456                         k=l=0;
3457                         sscanf(param[paramused], "%lf%n%*s%n",
3458                                                 &gdp->yrule,&k,&l);
3459                         if ((k!=0)&&(l==0)) paramused++;
3460                         if (paramused<paramcnt)
3461                             strcpy(gdp->legend,param[paramused++]);
3462                     }
3463                 } else {
3464                     if (j&0x10) { /* color present */
3465                         /* next should be legend or STACK.  If it
3466                         ** is STACK then leave it at is, else parse
3467                         ** the legend (if any)
3468                         */
3469                         if (paramused<paramcnt)
3470                             if (strcmp("STACK",param[paramused]))
3471                                 strcpy(gdp->legend,param[paramused++]);
3472                     }
3473                     if (paramused<paramcnt)
3474                         if (!strcmp("STACK",param[paramused])) {
3475                             gdp->stack=1;
3476                             paramused++;
3477                         }
3478                 }
3479                 break;
3480             case GF_PRINT:
3481                 im->prt_c++;
3482             case GF_GPRINT:
3483                 j=0;
3484                 sscanf(&line[argstart], DEF_NAM_FMT ":%n",gdp->vname,&j);
3485                 if (j==0) {
3486                     rrd_set_error("Cannot parse vname in line: '%s'",line);
3487                     break;
3488                 }
3489                 argstart+=j;
3490                 if (rrd_graph_check_vname(im,gdp->vname,line)) return;
3491                 j=0;
3492                 sscanf(&line[argstart], CF_NAM_FMT ":%n",symname,&j);
3493
3494                 k=(j!=0)?rrd_graph_check_CF(im,symname,line):1;
3495 #define VIDX im->gdes[gdp->vidx]
3496                 switch (k) {
3497                     case -1: /* looks CF but is not really CF */
3498                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3499                         break;
3500                     case  0: /* CF present and correct */
3501                         if (VIDX.gf == GF_VDEF)
3502                             rrd_set_error("Don't use CF when printing VDEF");
3503                         argstart+=j;
3504                         break;
3505                     case  1: /* CF not present */
3506                         if (VIDX.gf == GF_VDEF) rrd_clear_error();
3507                         else rrd_set_error("Printing DEF or CDEF needs CF");
3508                         break;
3509                     default:
3510                         rrd_set_error("Oops, bug in GPRINT scanning");
3511                 }
3512 #undef VIDX
3513                 if (rrd_test_error()) break;
3514
3515                 if (strlen(&line[argstart])!=0) {
3516                     if (rrd_graph_legend(gdp,&line[argstart])==0)
3517                         rrd_set_error("Cannot parse legend in line: %s",line);
3518                 } else rrd_set_error("No legend in (G)PRINT line: %s",line);
3519                 strcpy(gdp->format, gdp->legend);
3520                 break;
3521             case GF_DEF:
3522             case GF_VDEF:
3523             case GF_CDEF:
3524                 j=0;
3525                 if (paramcnt<2) {
3526                     rrd_set_error("Nothing following %s",param[0]);
3527                     break;
3528                 }
3529                 sscanf(param[1], DEF_NAM_FMT "=%n",gdp->vname,&j);
3530                 if (j==0) {
3531                     rrd_set_error("Could not parse %s:%s",param[0],param[1]);
3532                     break;
3533                 }
3534                 if (find_var(im,gdp->vname)!=-1) {
3535                     rrd_set_error("Variable '%s' in %s:%s' already in use\n",
3536                                                 gdp->vname,param[0],param[1]);
3537                     break;
3538                 }
3539                 paramused++;
3540                 argstart+=j;    /* parsed upto and including "xDEF:vname=" */
3541                 switch (gdp->gf) {
3542                     case GF_DEF:
3543                         if (strlen(&param[1][j])>MAXPATH) {
3544                             rrd_set_error("Path too long: %s:%s",param[0],param[1]);
3545                             break;
3546                         }
3547                         strcpy(gdp->rrd,&param[1][j]);
3548
3549                         if (paramcnt<3) {
3550                             rrd_set_error("No DS for %s:%s",param[0],param[1]);
3551                             break;
3552                         }
3553                         j=k=0;
3554                         sscanf(param[2],DS_NAM_FMT "%n%*s%n",gdp->ds_nam,&j,&k);
3555                         if ((j==0)||(k!=0)) {
3556                             rrd_set_error("Cannot parse DS in %s:%s:%s",
3557                                                 param[0],param[1],param[2]);
3558                             break;
3559                         }
3560                         paramused++;
3561                         if (paramcnt<4) {
3562                             rrd_set_error("No CF for %s:%s:%s",
3563                                         param[0],param[1],param[2]);
3564                             break;
3565                         }
3566                         j=k=0;
3567                         sscanf(param[3],CF_NAM_FMT "%n%*s%n",symname,&j,&k);
3568                         if ((j==0)||(k!=0)) {
3569                             rrd_set_error("Cannot parse CF in %s:%s:%s:%s",
3570                                         param[0],param[1],param[2],param[3]);
3571                             break;
3572                         }
3573                         if ((gdp->cf = cf_conv(symname))==-1) {
3574                             rrd_set_error("Unknown CF '%s' in %s:%s:%s:%s",
3575                                         param[0],param[1],param[2],param[3]);
3576                             break;
3577                         }
3578                         paramused++;
3579                         if (paramcnt>paramused) { /* optional parameters */
3580 rrd_graph_script_parse_def(paramcnt,param,paramused,gdp);
3581                             if (rrd_test_error()) break;
3582                         }
3583                         break;
3584                     case GF_VDEF:
3585                         k=0;
3586                         sscanf(&param[1][j],DEF_NAM_FMT ",%n",vname,&k);
3587                         if (k==0) {
3588                             rrd_set_error("Cannot parse vname: %s:%s",
3589                                                         param[0],param[1]);
3590                             break;
3591                         }
3592                         j+=k;
3593                         if (rrd_graph_check_vname(im,vname,line)) return;
3594                         if (       im->gdes[gdp->vidx].gf != GF_DEF
3595                                 && im->gdes[gdp->vidx].gf != GF_CDEF) {
3596                             rrd_set_error("variable '%s' not DEF nor "
3597                                 "CDEF in VDEF '%s'", vname,gdp->vname);
3598                             break;
3599                         }
3600                         vdef_parse(gdp,&param[1][j]);
3601                         break;
3602                     case GF_CDEF:
3603                         if ((gdp->rpnp = rpn_parse(
3604                                                 (void *)im,
3605                                                 &param[1][j],
3606                                                 &find_var_wrapper)
3607                                 )==NULL)
3608                             rrd_set_error("invalid rpn expression in: %s",param[1]);
3609                         break;
3610                     default: break;
3611                 }
3612                 break;
3613             default: rrd_set_error("Big oops");
3614         }
3615         if (rrd_test_error()) {
3616             im_free(im);
3617             return;
3618         }
3619     }
3620
3621     if (im->gdes_c==0){
3622         rrd_set_error("can't make a graph without contents");
3623         im_free(im); /* ??? is this set ??? */
3624         return; 
3625     }
3626 }
3627
3628 int
3629 rrd_graph_check_vname(image_desc_t *im, char *varname, char *err)
3630 {
3631     if ((im->gdes[im->gdes_c-1].vidx=find_var(im,varname))==-1) {
3632         rrd_set_error("Unknown variable '%s' in %s",varname,err);
3633         return -1;
3634     }
3635     return 0;
3636 }
3637 int
3638 rrd_graph_color(image_desc_t *im, char *var, char *err, int optional)
3639 {
3640     char *color;
3641     graph_desc_t *gdp=&im->gdes[im->gdes_c-1];
3642
3643     color=strstr(var,"#");
3644     if (color==NULL) {
3645         if (optional==0) {
3646             rrd_set_error("Found no color in %s",err);
3647             return 0;
3648         }
3649         return 0;
3650     } else {
3651         int n=0;
3652         char *rest;
3653         gfx_color_t    col;
3654
3655         rest=strstr(color,":");
3656         if (rest!=NULL)
3657             n=rest-color;
3658         else
3659             n=strlen(color);
3660
3661         switch (n) {
3662             case 7:
3663                 sscanf(color,"#%6lx%n",&col,&n);
3664                 col = (col << 8) + 0xff /* shift left by 8 */;
3665                 if (n!=7) rrd_set_error("Color problem in %s",err);
3666                 break;
3667             case 9:
3668                 sscanf(color,"#%8lx%n",&col,&n);
3669                 if (n==9) break;
3670             default:
3671                 rrd_set_error("Color problem in %s",err);
3672         }
3673         if (rrd_test_error()) return 0;
3674         gdp->col = col;
3675         return n;
3676     }
3677 }
3678 int
3679 rrd_graph_check_CF(image_desc_t *im, char *symname, char *err)
3680 {
3681     if ((im->gdes[im->gdes_c-1].cf=cf_conv(symname))==-1) {
3682         rrd_set_error("Unknown CF '%s' in %s",symname,err);
3683         return -1;
3684     }
3685     return 0;
3686 }
3687 int
3688 rrd_graph_legend(graph_desc_t *gdp, char *line)
3689 {
3690     int i;
3691
3692     i=scan_for_col(line,FMT_LEG_LEN,gdp->legend);
3693
3694     return (strlen(&line[i])==0);
3695 }
3696
3697
3698 int bad_format(char *fmt) {
3699     char *ptr;
3700     int n=0;
3701     ptr = fmt;
3702     while (*ptr != '\0')
3703         if (*ptr++ == '%') {
3704  
3705              /* line cannot end with percent char */
3706              if (*ptr == '\0') return 1;
3707  
3708              /* '%s', '%S' and '%%' are allowed */
3709              if (*ptr == 's' || *ptr == 'S' || *ptr == '%') ptr++;
3710
3711              /* or else '% 6.2lf' and such are allowed */
3712              else {
3713    
3714                  /* optional padding character */
3715                  if (*ptr == ' ' || *ptr == '+' || *ptr == '-') ptr++;
3716   
3717                  /* This should take care of 'm.n' with all three optional */
3718                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3719                  if (*ptr == '.') ptr++;
3720                  while (*ptr >= '0' && *ptr <= '9') ptr++;
3721   
3722                  /* Either 'le' or 'lf' must follow here */
3723                  if (*ptr++ != 'l') return 1;
3724                  if (*ptr == 'e' || *ptr == 'f') ptr++;
3725                  else return 1;
3726                  n++;
3727             }
3728          }
3729       
3730       return (n!=1); 
3731 }
3732
3733
3734 int
3735 vdef_parse(gdes,str)
3736 struct graph_desc_t *gdes;
3737 char *str;
3738 {
3739     /* A VDEF currently is either "func" or "param,func"
3740      * so the parsing is rather simple.  Change if needed.
3741      */
3742     double      param;
3743     char        func[30];
3744     int         n;
3745     
3746     n=0;
3747     sscanf(str,"%le,%29[A-Z]%n",&param,func,&n);
3748     if (n==strlen(str)) { /* matched */
3749         ;
3750     } else {
3751         n=0;
3752         sscanf(str,"%29[A-Z]%n",func,&n);
3753         if (n==strlen(str)) { /* matched */
3754             param=DNAN;
3755         } else {
3756             rrd_set_error("Unknown function string '%s' in VDEF '%s'"
3757                 ,str
3758                 ,gdes->vname
3759                 );
3760             return -1;
3761         }
3762     }
3763     if          (!strcmp("PERCENT",func)) gdes->vf.op = VDEF_PERCENT;
3764     else if     (!strcmp("MAXIMUM",func)) gdes->vf.op = VDEF_MAXIMUM;
3765     else if     (!strcmp("AVERAGE",func)) gdes->vf.op = VDEF_AVERAGE;
3766     else if     (!strcmp("MINIMUM",func)) gdes->vf.op = VDEF_MINIMUM;
3767     else if     (!strcmp("TOTAL",  func)) gdes->vf.op = VDEF_TOTAL;
3768     else if     (!strcmp("FIRST",  func)) gdes->vf.op = VDEF_FIRST;
3769     else if     (!strcmp("LAST",   func)) gdes->vf.op = VDEF_LAST;
3770     else {
3771         rrd_set_error("Unknown function '%s' in VDEF '%s'\n"
3772             ,func
3773             ,gdes->vname
3774             );
3775         return -1;
3776     };
3777
3778     switch (gdes->vf.op) {
3779         case VDEF_PERCENT:
3780             if (isnan(param)) { /* no parameter given */
3781                 rrd_set_error("Function '%s' needs parameter in VDEF '%s'\n"
3782                     ,func
3783                     ,gdes->vname
3784                     );
3785                 return -1;
3786             };
3787             if (param>=0.0 && param<=100.0) {
3788                 gdes->vf.param = param;
3789                 gdes->vf.val   = DNAN;  /* undefined */
3790                 gdes->vf.when  = 0;     /* undefined */
3791             } else {
3792                 rrd_set_error("Parameter '%f' out of range in VDEF '%s'\n"
3793                     ,param
3794                     ,gdes->vname
3795                     );
3796                 return -1;
3797             };
3798             break;
3799         case VDEF_MAXIMUM:
3800         case VDEF_AVERAGE:
3801         case VDEF_MINIMUM:
3802         case VDEF_TOTAL:
3803         case VDEF_FIRST:
3804         case VDEF_LAST:
3805             if (isnan(param)) {
3806                 gdes->vf.param = DNAN;
3807                 gdes->vf.val   = DNAN;
3808                 gdes->vf.when  = 0;
3809             } else {
3810                 rrd_set_error("Function '%s' needs no parameter in VDEF '%s'\n"
3811                     ,func
3812                     ,gdes->vname
3813                     );
3814                 return -1;
3815             };
3816             break;
3817     };
3818     return 0;
3819 }
3820
3821
3822 int
3823 vdef_calc(im,gdi)
3824 image_desc_t *im;
3825 int gdi;
3826 {
3827     graph_desc_t        *src,*dst;
3828     rrd_value_t         *data;
3829     long                step,steps;
3830
3831     dst = &im->gdes[gdi];
3832     src = &im->gdes[dst->vidx];
3833     data = src->data + src->ds;
3834     steps = (src->end - src->start) / src->step;
3835
3836 #if 0
3837 printf("DEBUG: start == %lu, end == %lu, %lu steps\n"
3838     ,src->start
3839     ,src->end
3840     ,steps
3841     );
3842 #endif
3843
3844     switch (dst->vf.op) {
3845         case VDEF_PERCENT: {
3846                 rrd_value_t *   array;
3847                 int             field;
3848
3849
3850                 if ((array = malloc(steps*sizeof(double)))==NULL) {
3851                     rrd_set_error("malloc VDEV_PERCENT");
3852                     return -1;
3853                 }
3854                 for (step=0;step < steps; step++) {
3855                     array[step]=data[step*src->ds_cnt];
3856                 }
3857                 qsort(array,step,sizeof(double),vdef_percent_compar);
3858
3859                 field = (steps-1)*dst->vf.param/100;
3860                 dst->vf.val  = array[field];
3861                 dst->vf.when = 0;       /* no time component */
3862 #if 0
3863 for(step=0;step<steps;step++)
3864 printf("DEBUG: %3li:%10.2f %c\n",step,array[step],step==field?'*':' ');
3865 #endif
3866             }
3867             break;
3868         case VDEF_MAXIMUM:
3869             step=0;
3870             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3871             if (step == steps) {
3872                 dst->vf.val  = DNAN;
3873                 dst->vf.when = 0;
3874             } else {
3875                 dst->vf.val  = data[step*src->ds_cnt];
3876                 dst->vf.when = src->start + (step+1)*src->step;
3877             }
3878             while (step != steps) {
3879                 if (finite(data[step*src->ds_cnt])) {
3880                     if (data[step*src->ds_cnt] > dst->vf.val) {
3881                         dst->vf.val  = data[step*src->ds_cnt];
3882                         dst->vf.when = src->start + (step+1)*src->step;
3883                     }
3884                 }
3885                 step++;
3886             }
3887             break;
3888         case VDEF_TOTAL:
3889         case VDEF_AVERAGE: {
3890             int cnt=0;
3891             double sum=0.0;
3892             for (step=0;step<steps;step++) {
3893                 if (finite(data[step*src->ds_cnt])) {
3894                     sum += data[step*src->ds_cnt];
3895                     cnt ++;
3896                 };
3897             }
3898             if (cnt) {
3899                 if (dst->vf.op == VDEF_TOTAL) {
3900                     dst->vf.val  = sum*src->step;
3901                     dst->vf.when = cnt*src->step;       /* not really "when" */
3902                 } else {
3903                     dst->vf.val = sum/cnt;
3904                     dst->vf.when = 0;   /* no time component */
3905                 };
3906             } else {
3907                 dst->vf.val  = DNAN;
3908                 dst->vf.when = 0;
3909             }
3910             }
3911             break;
3912         case VDEF_MINIMUM:
3913             step=0;
3914             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3915             if (step == steps) {
3916                 dst->vf.val  = DNAN;
3917                 dst->vf.when = 0;
3918             } else {
3919                 dst->vf.val  = data[step*src->ds_cnt];
3920                 dst->vf.when = src->start + (step+1)*src->step;
3921             }
3922             while (step != steps) {
3923                 if (finite(data[step*src->ds_cnt])) {
3924                     if (data[step*src->ds_cnt] < dst->vf.val) {
3925                         dst->vf.val  = data[step*src->ds_cnt];
3926                         dst->vf.when = src->start + (step+1)*src->step;
3927                     }
3928                 }
3929                 step++;
3930             }
3931             break;
3932         case VDEF_FIRST:
3933             /* The time value returned here is one step before the
3934              * actual time value.  This is the start of the first
3935              * non-NaN interval.
3936              */
3937             step=0;
3938             while (step != steps && isnan(data[step*src->ds_cnt])) step++;
3939             if (step == steps) { /* all entries were NaN */
3940                 dst->vf.val  = DNAN;
3941                 dst->vf.when = 0;
3942             } else {
3943                 dst->vf.val  = data[step*src->ds_cnt];
3944                 dst->vf.when = src->start + step*src->step;
3945             }
3946             break;
3947         case VDEF_LAST:
3948             /* The time value returned here is the
3949              * actual time value.  This is the end of the last
3950              * non-NaN interval.
3951              */
3952             step=steps-1;
3953             while (step >= 0 && isnan(data[step*src->ds_cnt])) step--;
3954             if (step < 0) { /* all entries were NaN */
3955                 dst->vf.val  = DNAN;
3956                 dst->vf.when = 0;
3957             } else {
3958                 dst->vf.val  = data[step*src->ds_cnt];
3959                 dst->vf.when = src->start + (step+1)*src->step;
3960             }
3961             break;
3962     }
3963     return 0;
3964 }
3965
3966 /* NaN < -INF < finite_values < INF */
3967 int
3968 vdef_percent_compar(a,b)
3969 const void *a,*b;
3970 {
3971     /* Equality is not returned; this doesn't hurt except
3972      * (maybe) for a little performance.
3973      */
3974
3975     /* First catch NaN values. They are smallest */
3976     if (isnan( *(double *)a )) return -1;
3977     if (isnan( *(double *)b )) return  1;
3978
3979     /* NaN doesn't reach this part so INF and -INF are extremes.
3980      * The sign from isinf() is compatible with the sign we return
3981      */
3982     if (isinf( *(double *)a )) return isinf( *(double *)a );
3983     if (isinf( *(double *)b )) return isinf( *(double *)b );
3984
3985     /* If we reach this, both values must be finite */
3986     if ( *(double *)a < *(double *)b ) return -1; else return 1;
3987 }