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