fixed path to rrd_format.h in tcl bindings
[rrdtool.git] / bindings / tcl / tclrrd.c
1 /*
2  * tclrrd.c -- A TCL interpreter extension to access the RRD library.
3  *
4  * Copyright (c) 1999,2000 Frank Strauss, Technical University of Braunschweig.
5  *
6  * Thread-safe code copyright (c) 2005 Oleg Derevenetz, CenterTelecom Voronezh ISP.
7  *
8  * See the file "COPYING" for information on usage and redistribution
9  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10  *
11  * $Id$
12  */
13
14
15
16 #include <errno.h>
17 #include <string.h>
18 #include <time.h>
19 #include <unistd.h>
20 #include <tcl.h>
21 #include "../../src/rrd_tool.h"
22 #include "../../src/rrd_format.h"
23
24 /* support pre-8.4 tcl */
25
26 #ifndef CONST84
27 #   define CONST84
28 #endif
29
30 extern int Tclrrd_Init(Tcl_Interp *interp);
31 extern int Tclrrd_SafeInit(Tcl_Interp *interp);
32
33
34 /*
35  * some rrd_XXX() and new thread-safe versions of Rrd_XXX()
36  * functions might modify the argv strings passed to it.
37  * Hence, we need to do some preparation before
38  * calling the rrd library functions.
39  */
40 static char ** getopt_init(int argc, CONST84 char *argv[])
41 {
42     char **argv2;
43     int i;
44     
45     argv2 = calloc(argc, sizeof(char *));
46     for (i = 0; i < argc; i++) {
47         argv2[i] = strdup(argv[i]);
48     }
49     return argv2;
50 }
51
52 static void getopt_cleanup(int argc, char **argv2)
53 {
54     int i;
55     
56     for (i = 0; i < argc; i++) {
57         if (argv2[i] != NULL) {
58             free(argv2[i]);
59         }
60     }
61     free(argv2);
62 }
63
64 static void getopt_free_element(argv2, argn)
65     char *argv2[];
66     int  argn;
67 {
68     if (argv2[argn] != NULL) {
69         free(argv2[argn]);
70         argv2[argn] = NULL;
71     }
72 }
73
74 static void getopt_squieeze(argc, argv2)
75     int  *argc;
76     char *argv2[];
77 {
78     int i, null_i = 0, argc_tmp = *argc;
79
80     for (i = 0; i < argc_tmp; i++) {
81         if (argv2[i] == NULL) {
82             (*argc)--;
83         } else {
84             argv2[null_i++] = argv2[i];
85         }
86     }
87 }
88
89
90
91 /* Thread-safe version */
92 static int
93 Rrd_Create(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
94 {
95     int                         argv_i;
96     char                        **argv2;
97     char                        *parsetime_error = NULL;
98     time_t                      last_up = time(NULL) - 10;
99     long int                    long_tmp;
100     unsigned long int           pdp_step = 300;
101     struct rrd_time_value       last_up_tv;
102
103     argv2 = getopt_init(argc, argv);
104
105     for (argv_i = 1; argv_i < argc; argv_i++) {
106         if (!strcmp(argv2[argv_i], "--start") || !strcmp(argv2[argv_i], "-b")) {
107             if (argv_i++>=argc) {
108                 Tcl_AppendResult(interp, "RRD Error: option '",
109                                  argv2[argv_i - 1], "' needs an argument", (char *) NULL);
110                 getopt_cleanup(argc, argv2);
111                 return TCL_ERROR;
112             }
113             if ((parsetime_error = parsetime(argv2[argv_i], &last_up_tv))) {
114                 Tcl_AppendResult(interp, "RRD Error: invalid time format: '",
115                                  argv2[argv_i], "'", (char *) NULL);
116                 getopt_cleanup(argc, argv2);
117                 return TCL_ERROR;
118             }
119             if (last_up_tv.type == RELATIVE_TO_END_TIME ||
120                 last_up_tv.type == RELATIVE_TO_START_TIME) {
121                 Tcl_AppendResult(interp, "RRD Error: specifying time relative to the 'start' ",
122                                  "or 'end' makes no sense here", (char *) NULL);
123                 getopt_cleanup(argc, argv2);
124                 return TCL_ERROR;
125             }
126             last_up = mktime(&last_up_tv.tm) + last_up_tv.offset;
127             if (last_up < 3600*24*365*10) {
128                 Tcl_AppendResult(interp, "RRD Error: the first entry to the RRD should be after 1980",
129                                  (char *) NULL);
130                 getopt_cleanup(argc, argv2);
131                 return TCL_ERROR;
132             }
133             getopt_free_element(argv2, argv_i - 1);
134             getopt_free_element(argv2, argv_i);
135         } else if (!strcmp(argv2[argv_i], "--step") || !strcmp(argv2[argv_i], "-s")) {
136             if (argv_i++>=argc) {
137                 Tcl_AppendResult(interp, "RRD Error: option '",
138                                  argv2[argv_i - 1], "' needs an argument", (char *) NULL);
139                 getopt_cleanup(argc, argv2);
140                 return TCL_ERROR;
141             }
142             long_tmp = atol(argv2[argv_i]);
143             if (long_tmp < 1) {
144                 Tcl_AppendResult(interp, "RRD Error: step size should be no less than one second",
145                                  (char *) NULL);
146                 getopt_cleanup(argc, argv2);
147                 return TCL_ERROR;
148             }
149             pdp_step = long_tmp;
150             getopt_free_element(argv2, argv_i - 1);
151             getopt_free_element(argv2, argv_i);
152         } else if (!strcmp(argv2[argv_i], "--")) {
153             getopt_free_element(argv2, argv_i);
154             break;
155         } else if (argv2[argv_i][0]=='-') {
156             Tcl_AppendResult(interp, "RRD Error: unknown option '",
157                              argv2[argv_i], "'", (char *) NULL);
158             getopt_cleanup(argc, argv2);
159             return TCL_ERROR;
160         }
161     }
162
163     getopt_squieeze(&argc, argv2);
164
165     if (argc < 2) {
166         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
167                          (char *) NULL);
168         getopt_cleanup(argc, argv2);
169         return TCL_ERROR;
170     }
171
172     rrd_create_r(argv2[1], pdp_step, last_up, argc - 2, argv2 + 2);
173
174     getopt_cleanup(argc, argv2);
175     
176     if (rrd_test_error()) {
177         Tcl_AppendResult(interp, "RRD Error: ",
178                          rrd_get_error(), (char *) NULL);
179         rrd_clear_error();
180         return TCL_ERROR;
181     }
182
183     return TCL_OK;
184 }
185
186
187
188 /* Thread-safe version */
189 static int
190 Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
191 {
192     if (argc < 2) {
193         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
194                          (char *) NULL);
195         return TCL_ERROR;
196     }
197
198     rrd_dump_r(argv[1], NULL);
199
200     /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
201
202     if (rrd_test_error()) {
203         Tcl_AppendResult(interp, "RRD Error: ",
204                          rrd_get_error(), (char *) NULL);
205         rrd_clear_error();
206         return TCL_ERROR;
207     }
208
209     return TCL_OK;
210 }
211
212
213
214 /* Thread-safe version */
215 static int
216 Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
217 {
218     time_t t;
219     
220     if (argc < 2) {
221         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
222                          (char *) NULL);
223         return TCL_ERROR;
224     }
225
226     t = rrd_last_r(argv[1]);
227
228     if (rrd_test_error()) {
229         Tcl_AppendResult(interp, "RRD Error: ",
230                          rrd_get_error(), (char *) NULL);
231         rrd_clear_error();
232         return TCL_ERROR;
233     }
234
235     Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
236
237     return TCL_OK;
238 }
239
240
241
242 /* Thread-safe version */
243 static int
244 Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
245 {
246     int         argv_i;
247     char        **argv2, *template = NULL;
248     
249     argv2 = getopt_init(argc, argv);
250
251     for (argv_i = 1; argv_i < argc; argv_i++) {
252         if (!strcmp(argv2[argv_i], "--template") || !strcmp(argv2[argv_i], "-t")) {
253             if (argv_i++>=argc) {
254                 Tcl_AppendResult(interp, "RRD Error: option '",
255                                  argv2[argv_i - 1], "' needs an argument", (char *) NULL);
256                 if (template != NULL) {
257                     free(template);
258                 }
259                 getopt_cleanup(argc, argv2);
260                 return TCL_ERROR;
261             }
262             if (template != NULL) {
263                 free(template);
264             }
265             template = strdup(argv2[argv_i]);
266             getopt_free_element(argv2, argv_i - 1);
267             getopt_free_element(argv2, argv_i);
268         } else if (!strcmp(argv2[argv_i], "--")) {
269             getopt_free_element(argv2, argv_i);
270             break;
271         } else if (argv2[argv_i][0]=='-') {
272             Tcl_AppendResult(interp, "RRD Error: unknown option '",
273                              argv2[argv_i], "'", (char *) NULL);
274             if (template != NULL) {
275                 free(template);
276             }
277             getopt_cleanup(argc, argv2);
278             return TCL_ERROR;
279         }
280     }
281
282     getopt_squieeze(&argc, argv2);
283
284     if (argc < 2) {
285         Tcl_AppendResult(interp, "RRD Error: needs rrd filename",
286                          (char *) NULL);
287         if (template != NULL) {
288             free(template);
289         }
290         getopt_cleanup(argc, argv2);
291         return TCL_ERROR;
292     }
293
294     rrd_update_r(argv2[1], template, argc - 2, argv2 + 2);
295
296     if (template != NULL) {
297         free(template);
298     }
299     getopt_cleanup(argc, argv2);
300
301     if (rrd_test_error()) {
302         Tcl_AppendResult(interp, "RRD Error: ",
303                          rrd_get_error(), (char *) NULL);
304         rrd_clear_error();
305         return TCL_ERROR;
306     }
307
308     return TCL_OK;
309 }
310
311 static int
312 Rrd_Lastupdate(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
313 {
314    time_t last_update;
315    char **argv2;
316    char **ds_namv;
317    char **last_ds;
318    char s[30];
319    Tcl_Obj *listPtr;
320    unsigned long ds_cnt, i;
321
322    argv2 = getopt_init(argc, argv);
323    if (rrd_lastupdate(argc-1, argv2, &last_update,
324        &ds_cnt, &ds_namv, &last_ds) == 0) {
325            listPtr = Tcl_GetObjResult(interp);
326            for (i=0; i<ds_cnt; i++) {
327                sprintf(s, " %28s", ds_namv[i]);
328                Tcl_ListObjAppendElement(interp, listPtr,
329                        Tcl_NewStringObj(s, -1));
330            sprintf(s, "\n\n%10lu:", last_update);
331                Tcl_ListObjAppendElement(interp, listPtr,
332                        Tcl_NewStringObj(s, -1));
333            for (i=0; i<ds_cnt; i++) {
334                sprintf(s, " %s", last_ds[i]);
335                Tcl_ListObjAppendElement(interp, listPtr,
336                        Tcl_NewStringObj(s, -1));
337                free(last_ds[i]);
338                free(ds_namv[i]);
339            }
340            sprintf(s, "\n");
341            Tcl_ListObjAppendElement(interp, listPtr,
342                     Tcl_NewStringObj(s, -1));
343            free(last_ds);
344            free(ds_namv);
345           }
346     }
347     return TCL_OK;
348 }
349
350 static int
351 Rrd_Fetch(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
352 {
353     time_t start, end, j;
354     unsigned long step, ds_cnt, i, ii;
355     rrd_value_t *data, *datai;
356     char **ds_namv;
357     Tcl_Obj *listPtr;
358     char s[30];
359     char **argv2;
360     
361     argv2 = getopt_init(argc, argv);
362     if (rrd_fetch(argc, argv2, &start, &end, &step,
363                   &ds_cnt, &ds_namv, &data) != -1) {
364         datai = data;
365         listPtr = Tcl_GetObjResult(interp);
366         for (j = start; j <= end; j += step) {
367             for (ii = 0; ii < ds_cnt; ii++) {
368                 sprintf(s, "%.2f", *(datai++));
369                 Tcl_ListObjAppendElement(interp, listPtr,
370                                          Tcl_NewStringObj(s, -1));
371             }
372         }
373         for (i=0; i<ds_cnt; i++) free(ds_namv[i]);
374         free(ds_namv);
375         free(data);
376     }
377     getopt_cleanup(argc, argv2);
378
379     if (rrd_test_error()) {
380         Tcl_AppendResult(interp, "RRD Error: ",
381                          rrd_get_error(), (char *) NULL);
382         rrd_clear_error();
383         return TCL_ERROR;
384     }
385
386     return TCL_OK;
387 }
388
389
390
391 static int
392 Rrd_Graph(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
393 {
394     Tcl_Channel channel;
395     int mode, fd2;
396     ClientData fd1;
397     FILE *stream = NULL;
398     char **calcpr = NULL;
399     int rc, xsize, ysize;
400     double ymin, ymax;
401     char dimensions[50];
402     char **argv2;
403     CONST84 char *save;
404     
405     /*
406      * If the "filename" is a Tcl fileID, then arrange for rrd_graph() to write to
407      * that file descriptor.  Will this work with windoze?  I have no idea.
408      */
409     if ((channel = Tcl_GetChannel(interp, argv[1], &mode)) != NULL) {
410         /*
411          * It >is< a Tcl fileID
412          */
413         if (!(mode & TCL_WRITABLE)) {
414             Tcl_AppendResult(interp, "channel \"", argv[1],
415                 "\" wasn't opened for writing", (char *) NULL);
416             return TCL_ERROR;
417         }
418         /*
419          * Must flush channel to make sure any buffered data is written before
420          * rrd_graph() writes to the stream
421          */
422         if (Tcl_Flush(channel) != TCL_OK) {
423             Tcl_AppendResult(interp, "flush failed for \"", argv[1], "\": ",
424                 strerror(Tcl_GetErrno()), (char *) NULL);
425             return TCL_ERROR;
426         }
427         if (Tcl_GetChannelHandle(channel, TCL_WRITABLE, &fd1) != TCL_OK) {
428             Tcl_AppendResult(interp, "cannot get file descriptor associated with \"",
429                 argv[1], "\"", (char *) NULL);
430             return TCL_ERROR;
431         }
432         /*
433          * Must dup() file descriptor so we can fclose(stream), otherwise the fclose()
434          * would close Tcl's file descriptor
435          */
436         if ((fd2 = dup((int)fd1)) == -1) {
437             Tcl_AppendResult(interp, "dup() failed for file descriptor associated with \"",
438                 argv[1], "\": ", strerror(errno), (char *) NULL);
439             return TCL_ERROR;
440         }
441         /*
442          * rrd_graph() wants a FILE*
443          */
444         if ((stream = fdopen(fd2, "wb")) == NULL) {
445             Tcl_AppendResult(interp, "fdopen() failed for file descriptor associated with \"",
446                 argv[1], "\": ", strerror(errno), (char *) NULL);
447             close(fd2);         /* plug potential file descriptor leak */
448             return TCL_ERROR;
449         }
450
451         save = argv[1];
452         argv[1] = "-";
453         argv2 = getopt_init(argc, argv);
454         argv[1] = save;
455     } else {
456         Tcl_ResetResult(interp);        /* clear error from Tcl_GetChannel() */
457         argv2 = getopt_init(argc, argv);
458     }
459
460     rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin, &ymax);
461     getopt_cleanup(argc, argv2);
462
463     if (stream != NULL)
464         fclose(stream);         /* plug potential malloc & file descriptor leak */
465
466     if (rc != -1) {
467         sprintf(dimensions, "%d %d", xsize, ysize);
468         Tcl_AppendResult(interp, dimensions, (char *) NULL);
469         if (calcpr) {
470 #if 0
471             int i;
472             
473             for(i = 0; calcpr[i]; i++){
474                 printf("%s\n", calcpr[i]);
475                 free(calcpr[i]);
476             } 
477 #endif
478             free(calcpr);
479         }
480     }
481
482     if (rrd_test_error()) {
483         Tcl_AppendResult(interp, "RRD Error: ",
484                          rrd_get_error(), (char *) NULL);
485         rrd_clear_error();
486         return TCL_ERROR;
487     }
488
489     return TCL_OK;
490 }
491
492
493
494 static int
495 Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
496 {
497     char **argv2;
498     
499     argv2 = getopt_init(argc, argv);
500     rrd_tune(argc, argv2);
501     getopt_cleanup(argc, argv2);
502
503     if (rrd_test_error()) {
504         Tcl_AppendResult(interp, "RRD Error: ",
505                          rrd_get_error(), (char *) NULL);
506         rrd_clear_error();
507         return TCL_ERROR;
508     }
509
510     return TCL_OK;
511 }
512
513
514
515 static int
516 Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
517 {
518     char **argv2;
519     
520     argv2 = getopt_init(argc, argv);
521     rrd_resize(argc, argv2);
522     getopt_cleanup(argc, argv2);
523
524     if (rrd_test_error()) {
525         Tcl_AppendResult(interp, "RRD Error: ",
526                          rrd_get_error(), (char *) NULL);
527         rrd_clear_error();
528         return TCL_ERROR;
529     }
530
531     return TCL_OK;
532 }
533
534
535
536 static int
537 Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
538 {
539     char **argv2;
540     
541     argv2 = getopt_init(argc, argv);
542     rrd_restore(argc, argv2);
543     getopt_cleanup(argc, argv2);
544
545     if (rrd_test_error()) {
546         Tcl_AppendResult(interp, "RRD Error: ",
547                          rrd_get_error(), (char *) NULL);
548         rrd_clear_error();
549         return TCL_ERROR;
550     }
551
552     return TCL_OK;
553 }
554
555
556
557 /*
558  * The following structure defines the commands in the Rrd extension.
559  */
560
561 typedef struct {
562     char *name;                 /* Name of the command. */
563     Tcl_CmdProc *proc;          /* Procedure for command. */
564     int hide;                   /* Hide if safe interpreter */
565 } CmdInfo;
566
567 static CmdInfo rrdCmds[] = {
568     { "Rrd::create",     Rrd_Create,     1 }, /* Thread-safe version */
569     { "Rrd::dump",       Rrd_Dump,       0 }, /* Thread-safe version */
570     { "Rrd::last",       Rrd_Last,       0 }, /* Thread-safe version */
571     { "Rrd::lastupdate", Rrd_Lastupdate, 0 }, /* Thread-safe version */
572     { "Rrd::update",     Rrd_Update,     1 }, /* Thread-safe version */
573     { "Rrd::fetch",      Rrd_Fetch,      0 },
574     { "Rrd::graph",      Rrd_Graph,      1 }, /* Due to RRD's API, a safe
575                                                 interpreter cannot create
576                                                 a graph since it writes to
577                                                 a filename supplied by the
578                                                 caller */
579     { "Rrd::tune",       Rrd_Tune,       1 },
580     { "Rrd::resize",     Rrd_Resize,     1 },
581     { "Rrd::restore",    Rrd_Restore,    1 },
582     { (char *) NULL,    (Tcl_CmdProc *)  NULL, 0        }
583 };
584
585
586
587 static int
588 init(Tcl_Interp *interp, int safe)
589
590     CmdInfo *cmdInfoPtr;
591     Tcl_CmdInfo info;
592
593     if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL )
594         return TCL_ERROR;
595
596     if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
597         return TCL_ERROR;
598     }
599
600     /*
601      * Why a global array?  In keeping with the Rrd:: namespace, why
602      * not simply create a normal variable Rrd::version and set it?
603      */
604     Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY);
605
606     for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
607         /*
608          * Check if the command already exists and return an error
609          * to ensure we detect name clashes while loading the Rrd
610          * extension.
611          */
612         if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) {
613             Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name,
614                              "\" already exists", (char *) NULL);
615             return TCL_ERROR;
616         }
617         if (safe && cmdInfoPtr->hide) {
618 #if 0
619             /*
620              * Turns out the one cannot hide a command in a namespace
621              * due to a limitation of Tcl, one can only hide global
622              * commands.  Thus, if we created the commands without
623              * the Rrd:: namespace in a safe interpreter, then the
624              * "unsafe" commands could be hidden -- which would allow
625              * an owning interpreter either un-hiding them or doing
626              * an "interp invokehidden".  If the Rrd:: namespace is
627              * used, then it's still possible for the owning interpreter
628              * to fake out the missing commands:
629              *
630              *   # Make all Rrd::* commands available in master interperter
631              *   package require Rrd
632              *   set safe [interp create -safe]
633              *   # Make safe Rrd::* commands available in safe interperter
634              *   interp invokehidden $safe -global load ./tclrrd1.2.11.so
635              *   # Provide the safe interpreter with the missing commands
636              *   $safe alias Rrd::update do_update $safe
637              *   proc do_update {which_interp $args} {
638              *     # Do some checking maybe...
639              *       :
640              *     return [eval Rrd::update $args]
641              *   }
642              *
643              * Our solution for now is to just not create the "unsafe"
644              * commands in a safe interpreter.
645              */
646             if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) != TCL_OK)
647                 return TCL_ERROR;
648 #endif
649         }
650         else
651             Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc,
652                           (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
653     }
654
655     if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
656         return TCL_ERROR;
657     }
658
659     return TCL_OK;
660 }
661
662 int
663 Tclrrd_Init(Tcl_Interp *interp)
664
665   return init(interp, 0);
666 }
667
668 /*
669  * See the comments above and note how few commands are considered "safe"...
670  * Using rrdtool in a safe interpreter has very limited functionality.  It's
671  * tempting to just return TCL_ERROR and forget about it.
672  */
673 int
674 Tclrrd_SafeInit(Tcl_Interp *interp)
675
676   return init(interp, 1);
677 }