updates for tcl bindings by -- Dave Bodenstab <dave on bodenstab.org>
[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  * See the file "COPYING" for information on usage and redistribution
7  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
8  *
9  * $Id$
10  */
11
12
13
14 #include <time.h>
15 #include <tcl.h>
16 #include <rrd_tool.h>
17 #include <rrd_format.h>
18
19 extern int Tclrrd_Init(Tcl_Interp *interp);
20 extern int Tclrrd_SafeInit(Tcl_Interp *interp);
21
22
23 /*
24  * some rrd_XXX() functions might modify the argv strings passed to it.
25  * Hence, we need to do some preparation before
26  * calling the rrd library functions.
27  */
28 static char ** getopt_init(int argc, CONST84 char *argv[])
29 {
30     char **argv2;
31     int i;
32     
33     argv2 = calloc(argc, sizeof(char *));
34     for (i = 0; i < argc; i++) {
35         argv2[i] = strdup(argv[i]);
36     }
37     return argv2;
38 }
39
40 static void getopt_cleanup(int argc, char **argv2)
41 {
42     int i;
43     
44     for (i = 0; i < argc; i++) {
45         free(argv2[i]);
46     }
47     free(argv2);
48 }
49
50
51
52 static int
53 Rrd_Create(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
54 {
55     char **argv2;
56
57     argv2 = getopt_init(argc, argv);
58     rrd_create(argc, argv2);
59     getopt_cleanup(argc, argv2);
60     
61     if (rrd_test_error()) {
62         Tcl_AppendResult(interp, "RRD Error: ",
63                          rrd_get_error(), (char *) NULL);
64         rrd_clear_error();
65         return TCL_ERROR;
66     }
67
68     return TCL_OK;
69 }
70
71
72
73 static int
74 Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
75 {
76     char **argv2;
77     
78     argv2 = getopt_init(argc, argv);
79     rrd_dump(argc, argv2);
80     getopt_cleanup(argc, argv2);
81
82     /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
83
84     if (rrd_test_error()) {
85         Tcl_AppendResult(interp, "RRD Error: ",
86                          rrd_get_error(), (char *) NULL);
87         rrd_clear_error();
88         return TCL_ERROR;
89     }
90
91     return TCL_OK;
92 }
93
94
95
96 static int
97 Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
98 {
99     time_t t;
100     char **argv2;
101     
102     argv2 = getopt_init(argc, argv);
103     t = rrd_last(argc, argv2);
104     getopt_cleanup(argc, argv2);
105
106
107     if (rrd_test_error()) {
108         Tcl_AppendResult(interp, "RRD Error: ",
109                          rrd_get_error(), (char *) NULL);
110         rrd_clear_error();
111         return TCL_ERROR;
112     }
113
114     Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
115
116     return TCL_OK;
117 }
118
119
120
121 static int
122 Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
123 {
124     char **argv2;
125     
126     argv2 = getopt_init(argc, argv);
127     rrd_update(argc, argv2);
128     getopt_cleanup(argc, argv2);
129
130     if (rrd_test_error()) {
131         Tcl_AppendResult(interp, "RRD Error: ",
132                          rrd_get_error(), (char *) NULL);
133         rrd_clear_error();
134         return TCL_ERROR;
135     }
136
137     return TCL_OK;
138 }
139
140
141
142 static int
143 Rrd_Fetch(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
144 {
145     time_t start, end, j;
146     unsigned long step, ds_cnt, i, ii;
147     rrd_value_t *data, *datai;
148     char **ds_namv;
149     Tcl_Obj *listPtr;
150     char s[30];
151     char **argv2;
152     
153     argv2 = getopt_init(argc, argv);
154     if (rrd_fetch(argc, argv2, &start, &end, &step,
155                   &ds_cnt, &ds_namv, &data) != -1) {
156         datai = data;
157         listPtr = Tcl_GetObjResult(interp);
158         for (j = start; j <= end; j += step) {
159             for (ii = 0; ii < ds_cnt; ii++) {
160                 sprintf(s, "%.2f", *(datai++));
161                 Tcl_ListObjAppendElement(interp, listPtr,
162                                          Tcl_NewStringObj(s, -1));
163             }
164         }
165         for (i=0; i<ds_cnt; i++) free(ds_namv[i]);
166         free(ds_namv);
167         free(data);
168     }
169     getopt_cleanup(argc, argv2);
170
171     if (rrd_test_error()) {
172         Tcl_AppendResult(interp, "RRD Error: ",
173                          rrd_get_error(), (char *) NULL);
174         rrd_clear_error();
175         return TCL_ERROR;
176     }
177
178     return TCL_OK;
179 }
180
181
182
183 static int
184 Rrd_Graph(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
185 {
186     char **calcpr;
187     int xsize, ysize;
188     double ymin, ymax;
189     char dimensions[50];
190     char **argv2;
191     
192     calcpr = NULL;
193
194     argv2 = getopt_init(argc, argv);
195     if (rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax) != -1 ) {
196         sprintf(dimensions, "%d %d", xsize, ysize);
197         Tcl_AppendResult(interp, dimensions, (char *) NULL);
198         if (calcpr) {
199 #if 0
200             int i;
201             
202             for(i = 0; calcpr[i]; i++){
203                 printf("%s\n", calcpr[i]);
204                 free(calcpr[i]);
205             } 
206 #endif
207             free(calcpr);
208         }
209     }
210     getopt_cleanup(argc, argv2);
211
212     if (rrd_test_error()) {
213         Tcl_AppendResult(interp, "RRD Error: ",
214                          rrd_get_error(), (char *) NULL);
215         rrd_clear_error();
216         return TCL_ERROR;
217     }
218
219     return TCL_OK;
220 }
221
222
223
224 static int
225 Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
226 {
227     char **argv2;
228     
229     argv2 = getopt_init(argc, argv);
230     rrd_tune(argc, argv2);
231     getopt_cleanup(argc, argv2);
232
233     if (rrd_test_error()) {
234         Tcl_AppendResult(interp, "RRD Error: ",
235                          rrd_get_error(), (char *) NULL);
236         rrd_clear_error();
237         return TCL_ERROR;
238     }
239
240     return TCL_OK;
241 }
242
243
244
245 static int
246 Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
247 {
248     char **argv2;
249     
250     argv2 = getopt_init(argc, argv);
251     rrd_resize(argc, argv2);
252     getopt_cleanup(argc, argv2);
253
254     if (rrd_test_error()) {
255         Tcl_AppendResult(interp, "RRD Error: ",
256                          rrd_get_error(), (char *) NULL);
257         rrd_clear_error();
258         return TCL_ERROR;
259     }
260
261     return TCL_OK;
262 }
263
264
265
266 static int
267 Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
268 {
269     char **argv2;
270     
271     argv2 = getopt_init(argc, argv);
272     rrd_restore(argc, argv2);
273     getopt_cleanup(argc, argv2);
274
275     if (rrd_test_error()) {
276         Tcl_AppendResult(interp, "RRD Error: ",
277                          rrd_get_error(), (char *) NULL);
278         rrd_clear_error();
279         return TCL_ERROR;
280     }
281
282     return TCL_OK;
283 }
284
285
286
287 /*
288  * The following structure defines the commands in the Rrd extension.
289  */
290
291 typedef struct {
292     char *name;                 /* Name of the command. */
293     Tcl_CmdProc *proc;          /* Procedure for command. */
294     int hide;                   /* Hide if safe interpreter */
295 } CmdInfo;
296
297 static CmdInfo rrdCmds[] = {
298     { "Rrd::create",    Rrd_Create,     1 },
299     { "Rrd::dump",      Rrd_Dump,       0 },
300     { "Rrd::last",      Rrd_Last,       0 },
301     { "Rrd::update",    Rrd_Update,     1 },
302     { "Rrd::fetch",     Rrd_Fetch,      0 },
303     { "Rrd::graph",     Rrd_Graph,      1 }, /* Due to RRD's API, a safe
304                                                 interpreter cannot create
305                                                 a graph since it writes to
306                                                 a filename supplied by the
307                                                 caller */
308     { "Rrd::tune",      Rrd_Tune,       1 },
309     { "Rrd::resize",    Rrd_Resize,     1 },
310     { "Rrd::restore",   Rrd_Restore,    1 },
311     { (char *) NULL,    (Tcl_CmdProc *) NULL, 0 }
312 };
313
314
315
316 static int
317 init(Tcl_Interp *interp, int safe)
318
319     CmdInfo *cmdInfoPtr;
320     Tcl_CmdInfo info;
321
322     if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL )
323         return TCL_ERROR;
324
325     if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
326         return TCL_ERROR;
327     }
328
329     /*
330      * Why a global array?  In keeping with the Rrd:: namespace, why
331      * not simply create a normal variable Rrd::version and set it?
332      */
333     Tcl_SetVar2(interp, "rrd", "version", VERSION, TCL_GLOBAL_ONLY);
334
335     for (cmdInfoPtr = rrdCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
336         /*
337          * Check if the command already exists and return an error
338          * to ensure we detect name clashes while loading the Rrd
339          * extension.
340          */
341         if (Tcl_GetCommandInfo(interp, cmdInfoPtr->name, &info)) {
342             Tcl_AppendResult(interp, "command \"", cmdInfoPtr->name,
343                              "\" already exists", (char *) NULL);
344             return TCL_ERROR;
345         }
346         if (safe && cmdInfoPtr->hide) {
347 #if 0
348             /*
349              * Turns out the one cannot hide a command in a namespace
350              * due to a limitation of Tcl, one can only hide global
351              * commands.  Thus, if we created the commands without
352              * the Rrd:: namespace in a safe interpreter, then the
353              * "unsafe" commands could be hidden -- which would allow
354              * an owning interpreter either un-hiding them or doing
355              * an "interp invokehidden".  If the Rrd:: namespace is
356              * used, then it's still possible for the owning interpreter
357              * to fake out the missing commands:
358              *
359              *   # Make all Rrd::* commands available in master interperter
360              *   package require Rrd
361              *   set safe [interp create -safe]
362              *   # Make safe Rrd::* commands available in safe interperter
363              *   interp invokehidden $safe -global load ./tclrrd1.2.11.so
364              *   # Provide the safe interpreter with the missing commands
365              *   $safe alias Rrd::update do_update $safe
366              *   proc do_update {which_interp $args} {
367              *     # Do some checking maybe...
368              *       :
369              *     return [eval Rrd::update $args]
370              *   }
371              *
372              * Our solution for now is to just not create the "unsafe"
373              * commands in a safe interpreter.
374              */
375             if (Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name) != TCL_OK)
376                 return TCL_ERROR;
377 #endif
378         }
379         else
380             Tcl_CreateCommand(interp, cmdInfoPtr->name, cmdInfoPtr->proc,
381                           (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
382     }
383
384     if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
385         return TCL_ERROR;
386     }
387
388     return TCL_OK;
389 }
390
391 int
392 Tclrrd_Init(Tcl_Interp *interp)
393
394   return init(interp, 0);
395 }
396
397 /*
398  * See the comments above and note how few commands are considered "safe"...
399  * Using rrdtool in a safe interpreter has very limited functionality.  It's
400  * tempting to just return TCL_ERROR and forget about it.
401  */
402 int
403 Tclrrd_SafeInit(Tcl_Interp *interp)
404
405   return init(interp, 1);
406 }