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