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 */
14 #include <time.h>
15 #include <tcl.h>
16 #include <rrd_tool.h>
17 #include <rrd_format.h>
19 extern int Tclrrd_Init(Tcl_Interp *interp);
20 extern int Tclrrd_SafeInit(Tcl_Interp *interp);
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;
33 argv2 = calloc(argc, sizeof(char *));
34 for (i = 0; i < argc; i++) {
35 argv2[i] = strdup(argv[i]);
36 }
37 return argv2;
38 }
40 static void getopt_cleanup(int argc, char **argv2)
41 {
42 int i;
44 for (i = 0; i < argc; i++) {
45 free(argv2[i]);
46 }
47 free(argv2);
48 }
52 static int
53 Rrd_Create(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
54 {
55 char **argv2;
57 argv2 = getopt_init(argc, argv);
58 rrd_create(argc, argv2);
59 getopt_cleanup(argc, argv2);
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 }
68 return TCL_OK;
69 }
73 static int
74 Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
75 {
76 char **argv2;
78 argv2 = getopt_init(argc, argv);
79 rrd_dump(argc, argv2);
80 getopt_cleanup(argc, argv2);
82 /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
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 }
91 return TCL_OK;
92 }
96 static int
97 Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
98 {
99 time_t t;
100 char **argv2;
102 argv2 = getopt_init(argc, argv);
103 t = rrd_last(argc, argv2);
104 getopt_cleanup(argc, argv2);
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 }
114 Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
116 return TCL_OK;
117 }
121 static int
122 Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
123 {
124 char **argv2;
126 argv2 = getopt_init(argc, argv);
127 rrd_update(argc, argv2);
128 getopt_cleanup(argc, argv2);
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 }
137 return TCL_OK;
138 }
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;
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);
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 }
178 return TCL_OK;
179 }
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;
192 calcpr = NULL;
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;
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);
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 }
219 return TCL_OK;
220 }
224 static int
225 Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
226 {
227 char **argv2;
229 argv2 = getopt_init(argc, argv);
230 rrd_tune(argc, argv2);
231 getopt_cleanup(argc, argv2);
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 }
240 return TCL_OK;
241 }
245 static int
246 Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
247 {
248 char **argv2;
250 argv2 = getopt_init(argc, argv);
251 rrd_resize(argc, argv2);
252 getopt_cleanup(argc, argv2);
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 }
261 return TCL_OK;
262 }
266 static int
267 Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
268 {
269 char **argv2;
271 argv2 = getopt_init(argc, argv);
272 rrd_restore(argc, argv2);
273 getopt_cleanup(argc, argv2);
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 }
282 return TCL_OK;
283 }
287 /*
288 * The following structure defines the commands in the Rrd extension.
289 */
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;
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 };
316 static int
317 init(Tcl_Interp *interp, int safe)
318 {
319 CmdInfo *cmdInfoPtr;
320 Tcl_CmdInfo info;
322 if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL )
323 return TCL_ERROR;
325 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
326 return TCL_ERROR;
327 }
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);
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 }
384 if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
385 return TCL_ERROR;
386 }
388 return TCL_OK;
389 }
391 int
392 Tclrrd_Init(Tcl_Interp *interp)
393 {
394 return init(interp, 0);
395 }
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 }