af3237713b0a756c2459c61416f30baa9b91a82f
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 <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>
22 extern int Tclrrd_Init(Tcl_Interp *interp);
23 extern int Tclrrd_SafeInit(Tcl_Interp *interp);
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;
36 argv2 = calloc(argc, sizeof(char *));
37 for (i = 0; i < argc; i++) {
38 argv2[i] = strdup(argv[i]);
39 }
40 return argv2;
41 }
43 static void getopt_cleanup(int argc, char **argv2)
44 {
45 int i;
47 for (i = 0; i < argc; i++) {
48 free(argv2[i]);
49 }
50 free(argv2);
51 }
55 static int
56 Rrd_Create(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
57 {
58 char **argv2;
60 argv2 = getopt_init(argc, argv);
61 rrd_create(argc, argv2);
62 getopt_cleanup(argc, argv2);
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 }
71 return TCL_OK;
72 }
76 static int
77 Rrd_Dump(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
78 {
79 char **argv2;
81 argv2 = getopt_init(argc, argv);
82 rrd_dump(argc, argv2);
83 getopt_cleanup(argc, argv2);
85 /* NOTE: rrd_dump() writes to stdout. No interaction with TCL. */
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 }
94 return TCL_OK;
95 }
99 static int
100 Rrd_Last(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
101 {
102 time_t t;
103 char **argv2;
105 argv2 = getopt_init(argc, argv);
106 t = rrd_last(argc, argv2);
107 getopt_cleanup(argc, argv2);
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 }
117 Tcl_SetIntObj(Tcl_GetObjResult(interp), t);
119 return TCL_OK;
120 }
124 static int
125 Rrd_Update(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
126 {
127 char **argv2;
129 argv2 = getopt_init(argc, argv);
130 rrd_update(argc, argv2);
131 getopt_cleanup(argc, argv2);
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 }
140 return TCL_OK;
141 }
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;
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);
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 }
181 return TCL_OK;
182 }
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;
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 }
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 }
255 rc = rrd_graph(argc, argv2, &calcpr, &xsize, &ysize, stream, &ymin, &ymax);
256 getopt_cleanup(argc, argv2);
258 if (stream != NULL)
259 fclose(stream); /* plug potential malloc & file descriptor leak */
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;
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 }
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 }
284 return TCL_OK;
285 }
289 static int
290 Rrd_Tune(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
291 {
292 char **argv2;
294 argv2 = getopt_init(argc, argv);
295 rrd_tune(argc, argv2);
296 getopt_cleanup(argc, argv2);
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 }
305 return TCL_OK;
306 }
310 static int
311 Rrd_Resize(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
312 {
313 char **argv2;
315 argv2 = getopt_init(argc, argv);
316 rrd_resize(argc, argv2);
317 getopt_cleanup(argc, argv2);
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 }
326 return TCL_OK;
327 }
331 static int
332 Rrd_Restore(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])
333 {
334 char **argv2;
336 argv2 = getopt_init(argc, argv);
337 rrd_restore(argc, argv2);
338 getopt_cleanup(argc, argv2);
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 }
347 return TCL_OK;
348 }
352 /*
353 * The following structure defines the commands in the Rrd extension.
354 */
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;
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 };
381 static int
382 init(Tcl_Interp *interp, int safe)
383 {
384 CmdInfo *cmdInfoPtr;
385 Tcl_CmdInfo info;
387 if ( Tcl_InitStubs(interp,TCL_VERSION,0) == NULL )
388 return TCL_ERROR;
390 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 1) == NULL) {
391 return TCL_ERROR;
392 }
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);
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 }
449 if (Tcl_PkgProvide(interp, "Rrd", VERSION) != TCL_OK) {
450 return TCL_ERROR;
451 }
453 return TCL_OK;
454 }
456 int
457 Tclrrd_Init(Tcl_Interp *interp)
458 {
459 return init(interp, 0);
460 }
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 }