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